Current section

Files

Jump to
sidereon lib sidereon collision.ex
Raw

lib/sidereon/collision.ex

defmodule Sidereon.Collision do
@moduledoc """
Collision probability calculation for close approaches.
Computes `Pc` from relative state, covariance, and hard-body radius
using encounter-plane methods such as Foster 2D.
This module is intended for operational conjunction screening and
standards-based workflows such as CCSDS CDM ingestion.
The encounter geometry and `Pc` integration are implemented in the
`sidereon-core` core; this module validates the covariance inputs, selects
the method, marshals across the NIF, and packages the `%Result{}`.
"""
alias Sidereon.Collision.Result
alias Sidereon.Covariance
alias Sidereon.NIF
@type params :: %{
r1: {float(), float(), float()},
v1: {float(), float(), float()},
cov1: [[float()]],
r2: {float(), float(), float()},
v2: {float(), float(), float()},
cov2: [[float()]],
hard_body_radius_km: float()
}
# Maps the public method option to the core method atom and the method tag
# reported on the result.
@methods %{
equal_area: {:equal_area, :foster_2d_equal_area},
numerical: {:numerical, :foster_2d_numerical},
alfano_2005: {:alfano_2005, :alfano_2005}
}
@doc """
Compute collision probability from two objects' states and covariances.
All positions in km, velocities in km/s, covariances in km².
## Options
* `:method` - one of:
* `:equal_area` (default) - Foster 2D equal-area-square approximation
* `:numerical` - Foster 2D with polar-grid numerical integration
* `:alfano_2005` - Alfano (2005) 1D Simpson's rule with analytical
cross-axis integration. Independent cross-check against the Foster
methods.
Returns `{:ok, %Result{}}` or `{:error, reason}`.
"""
@spec probability(params(), keyword()) :: {:ok, Result.t()} | {:error, String.t()}
def probability(params, opts \\ []) do
%{cov1: cov1, cov2: cov2} = params
cond do
not Covariance.valid_matrix?(cov1) ->
{:error, "cov1 is not a 3x3 numeric matrix"}
not Covariance.valid_matrix?(cov2) ->
{:error, "cov2 is not a 3x3 numeric matrix"}
not Covariance.positive_semidefinite?(cov1) ->
{:error, "cov1 is not positive semidefinite"}
not Covariance.positive_semidefinite?(cov2) ->
{:error, "cov2 is not positive semidefinite"}
true ->
method = Keyword.get(opts, :method, :equal_area)
case Map.fetch(@methods, method) do
{:ok, {core_method, _}} -> run(params, core_method)
:error -> {:error, "unsupported method: #{method}"}
end
end
end
@doc """
Compute Pc using the 2D Foster method with equal-area square approximation.
"""
@spec probability_equal_area(params()) :: {:ok, Result.t()} | {:error, String.t()}
def probability_equal_area(params), do: run(params, :equal_area)
@doc """
Compute Pc using the 2D Foster method with numerical integration over the circle.
"""
@spec probability_numerical(params()) :: {:ok, Result.t()} | {:error, String.t()}
def probability_numerical(params), do: run(params, :numerical)
@doc """
Compute Pc using the Alfano (2005) method.
Uses 1D Simpson's composite rule along the principal x axis of the
encounter-plane covariance, with the cross-axis integration evaluated
analytically as the difference of two error functions. This uses a 1D
scan in place of the 2D quadrature grid used by `probability_numerical/1`
for elongated covariance cases.
Reference: Alfano, S., "A Numerical Implementation of Spherical Object
Collision Probability," Journal of the Astronautical Sciences, Vol. 53,
No. 1, Jan-Mar 2005, pp. 103-109.
"""
@spec probability_alfano_2005(params()) :: {:ok, Result.t()} | {:error, String.t()}
def probability_alfano_2005(params), do: run(params, :alfano_2005)
defp run(params, core_method) do
%{r1: r1, v1: v1, cov1: cov1, r2: r2, v2: v2, cov2: cov2, hard_body_radius_km: hbr} = params
{_, result_method} = Map.fetch!(@methods, core_method)
case NIF.collision_probability(r1, v1, cov1, r2, v2, cov2, hbr, core_method) do
{:ok, {pc, miss_km, relative_speed_km_s, sigma_x_km, sigma_z_km}} ->
{:ok,
%Result{
pc: pc,
miss_km: miss_km,
relative_speed_km_s: relative_speed_km_s,
sigma_x_km: sigma_x_km,
sigma_z_km: sigma_z_km,
method: result_method
}}
{:error, reason} ->
{:error, conjunction_reason(reason)}
end
end
# The core returns the zero-relative-velocity case as the `:undefined_frame`
# atom; surface it as this module's documented string reason.
defp conjunction_reason(:undefined_frame), do: "zero relative velocity"
defp conjunction_reason(reason), do: reason
end