///|
pub struct SafetyAssessment {
minimum_distance_km : Double
relative_speed_km_s : Double
closing_speed_km_s : Double
time_to_closest_s : Double
collision_probability_proxy : Double
safe : Bool
} derive(Debug, Eq)
///|
pub struct PerigeeApogee {
perigee_km : Double
apogee_km : Double
perigee_altitude_km : Double
apogee_altitude_km : Double
} derive(Debug, Eq)
///|
pub fn perigee_apogee(elements : ClassicalElements) -> PerigeeApogee {
let peri = elements.semi_major_axis_km * (1.0 - elements.eccentricity)
let apo = elements.semi_major_axis_km * (1.0 + elements.eccentricity)
{
perigee_km: peri,
apogee_km: apo,
perigee_altitude_km: peri - earth_radius_km,
apogee_altitude_km: apo - earth_radius_km,
}
}
///|
pub fn orbit_intersects_body(
elements : ClassicalElements,
body_radius_km : Double,
) -> Bool {
elements.semi_major_axis_km * (1.0 - elements.eccentricity) <= body_radius_km
}
///|
pub fn relative_state(a : StateVector, b : StateVector) -> StateVector {
StateVector::new(
a.position_km.sub(b.position_km),
a.velocity_km_s.sub(b.velocity_km_s),
)
}
///|
pub fn closest_approach(a : StateVector, b : StateVector) -> SafetyAssessment {
let relative = relative_state(a, b)
let speed2 = relative.velocity_km_s.norm_squared()
let time = if speed2 == 0.0 {
0.0
} else {
-relative.position_km.dot(relative.velocity_km_s) / speed2
}
let closest = relative.position_km.add(
relative.velocity_km_s.scale(time.max(0.0)),
)
let distance = closest.norm()
let closing = if relative.position_km.norm() == 0.0 {
0.0
} else {
-relative.position_km.dot(relative.velocity_km_s) /
relative.position_km.norm()
}
{
minimum_distance_km: distance,
relative_speed_km_s: relative.velocity_km_s.norm(),
closing_speed_km_s: closing.max(0.0),
time_to_closest_s: time.max(0.0),
collision_probability_proxy: @math.exp(-distance / 10.0),
safe: distance > 10.0,
}
}
///|
pub fn safety_margin_km(
assessment : SafetyAssessment,
required_distance_km : Double,
) -> Double {
assessment.minimum_distance_km - required_distance_km
}
///|
pub fn is_collision_risk(
assessment : SafetyAssessment,
threshold_km : Double,
) -> Bool {
assessment.minimum_distance_km <= threshold_km
}
///|
pub fn atmospheric_reentry_altitude_km() -> Double {
120.0
}
///|
pub fn is_reentry_risk(elements : ClassicalElements) -> Bool {
perigee_apogee(elements).perigee_altitude_km <
atmospheric_reentry_altitude_km()
}
///|
pub fn solar_escape_margin(
mu : Double,
radius_km : Double,
speed_km_s : Double,
) -> Double {
escape_velocity(mu, radius_km) - speed_km_s
}
///|
pub fn orbit_eccentricity_limit(elements : ClassicalElements) -> Double {
(1.0 - elements.eccentricity).max(0.0)
}
///|
pub fn assess_orbit(elements : ClassicalElements) -> Array[String] {
let issues : Array[String] = []
if is_reentry_risk(elements) {
issues.push("reentry")
}
if elements.eccentricity >= 1.0 {
issues.push("escape")
}
if elements.semi_major_axis_km <= 0.0 {
issues.push("invalid-axis")
}
issues
}
///|
pub fn conjunction_screen(
reference : StateVector,
objects : Array[StateVector],
threshold_km : Double,
) -> Array[SafetyAssessment] {
objects
.map(object => closest_approach(reference, object))
.filter(item => item.minimum_distance_km <= threshold_km)
}
///|
pub fn relative_motion_distance(
a : StateVector,
b : StateVector,
delta_t_s : Double,
) -> Double {
let relative = relative_state(a, b)
relative.position_km.add(relative.velocity_km_s.scale(delta_t_s)).norm()
}
///|
pub fn collision_probability_proxy(
distance_km : Double,
covariance_radius_km : Double,
) -> Double {
if covariance_radius_km <= 0.0 {
if distance_km <= 0.0 {
1.0
} else {
0.0
}
} else {
@math.exp(
-0.5 *
distance_km *
distance_km /
(covariance_radius_km * covariance_radius_km),
)
}
}