///|
pub struct LinkBudget {
frequency_ghz : Double
distance_km : Double
tx_power_dbw : Double
antenna_gain_db : Double
losses_db : Double
margin_db : Double
} derive(Debug, Eq)
///|
pub struct ContactCapacity {
duration_s : Double
rate_kbps : Double
volume_mb : Double
margin_db : Double
} derive(Debug, Eq)
///|
pub fn link_budget(
frequency_ghz : Double,
distance_km : Double,
tx_power_dbw : Double,
antenna_gain_db : Double,
losses_db : Double,
required_db : Double,
) -> LinkBudget {
let margin = antenna_gain_db +
tx_power_dbw -
losses_db -
free_space_path_loss_db(distance_km, frequency_ghz) -
required_db
{
frequency_ghz,
distance_km,
tx_power_dbw,
antenna_gain_db,
losses_db,
margin_db: margin,
}
}
///|
pub fn contact_capacity(
duration_s : Double,
rate_kbps : Double,
link : LinkBudget,
) -> ContactCapacity {
{
duration_s: duration_s.max(0.0),
rate_kbps: rate_kbps.max(0.0),
volume_mb: contact_data_volume_mb(rate_kbps, duration_s),
margin_db: link.margin_db,
}
}
///|
pub fn link_is_usable(budget : LinkBudget, required_margin_db : Double) -> Bool {
budget.margin_db >= required_margin_db
}
///|
pub fn doppler_corrected_frequency(
carrier_hz : Double,
radial_speed_km_s : Double,
) -> Double {
carrier_hz + doppler_shift_hz(carrier_hz, radial_speed_km_s)
}
///|
pub fn range_rate(a : StateVector, b : StateVector) -> Double {
let relative = relative_state(a, b)
if relative.position_km.norm() == 0.0 {
0.0
} else {
relative.position_km.dot(relative.velocity_km_s) /
relative.position_km.norm()
}
}
///|
pub fn round_trip_delay_s(distance_km : Double) -> Double {
distance_km.max(0.0) * 2.0 / 299792.458
}
///|
pub fn light_time_s(distance_km : Double) -> Double {
distance_km.max(0.0) / 299792.458
}
///|
pub fn packet_error_rate(eb_no_db : Double, coding_gain_db : Double) -> Double {
let quality = eb_no_db + coding_gain_db
if quality <= 0.0 {
1.0
} else {
@math.exp(-quality / 10.0).min(1.0)
}
}
///|
pub fn effective_data_rate(rate_kbps : Double, packet_error : Double) -> Double {
rate_kbps.max(0.0) * (1.0 - clamp(packet_error, 0.0, 1.0))
}
///|
pub fn link_availability(
margins : Array[Double],
threshold_db : Double,
) -> Double {
if margins.length() == 0 {
0.0
} else {
Double::from_int(margins.filter(margin => margin >= threshold_db).length()) /
Double::from_int(margins.length())
}
}