///|
pub struct MissionPhase {
name : String
start_s : Double
end_s : Double
delta_v_km_s : Double
priority : Int
resource_cost : Double
} derive(Debug, Eq)
///|
pub struct MissionScenario {
name : String
phases : Array[MissionPhase]
reserve_km_s : Double
} derive(Debug, Eq)
///|
pub struct ScenarioSummary {
valid : Bool
phase_count : Int
total_duration_s : Double
total_delta_v_km_s : Double
total_resource_cost : Double
overlap_count : Int
resource_remaining : Double
} derive(Debug, Eq)
///|
pub fn mission_phase(
name : String,
start_s : Double,
end_s : Double,
delta_v_km_s : Double,
) -> MissionPhase {
{
name,
start_s,
end_s,
delta_v_km_s: delta_v_km_s.max(0.0),
priority: 0,
resource_cost: delta_v_km_s.max(0.0),
}
}
///|
pub fn MissionPhase::with_priority(
phase : MissionPhase,
priority : Int,
) -> MissionPhase {
{ ..phase, priority, }
}
///|
pub fn MissionPhase::with_resource_cost(
phase : MissionPhase,
resource_cost : Double,
) -> MissionPhase {
{ ..phase, resource_cost: resource_cost.max(0.0) }
}
///|
pub fn phase_duration_s(phase : MissionPhase) -> Double {
(phase.end_s - phase.start_s).max(0.0)
}
///|
pub fn phase_is_well_formed(phase : MissionPhase) -> Bool {
phase.name.length() > 0 &&
phase.start_s >= 0.0 &&
phase.end_s >= phase.start_s &&
phase.delta_v_km_s >= 0.0 &&
phase.resource_cost >= 0.0
}
///|
pub fn phase_contains(phase : MissionPhase, time_s : Double) -> Bool {
time_s >= phase.start_s && time_s <= phase.end_s
}
///|
pub fn MissionScenario::new(name : String) -> MissionScenario {
{ name, phases: [], reserve_km_s: 0.0 }
}
///|
pub fn MissionScenario::with_reserve(
scenario : MissionScenario,
reserve_km_s : Double,
) -> MissionScenario {
{ ..scenario, reserve_km_s: reserve_km_s.max(0.0) }
}
///|
pub fn MissionScenario::add_phase(
scenario : MissionScenario,
phase : MissionPhase,
) -> MissionScenario {
let phases = scenario.phases.copy()
phases.push(phase)
{ ..scenario, phases, }
}
///|
pub fn MissionScenario::sort_by_start(
scenario : MissionScenario,
) -> MissionScenario {
let phases = scenario.phases.copy()
for i in 0.. Bool {
a.start_s < b.end_s && b.start_s < a.end_s
}
///|
pub fn count_phase_overlaps(scenario : MissionScenario) -> Int {
let ordered = scenario.sort_by_start()
let mut count = 0
for i in 0.. Double {
scenario.phases.fold(init=0.0, (maximum, phase) => maximum.max(phase.end_s))
}
///|
pub fn scenario_delta_v(scenario : MissionScenario) -> Double {
scenario.phases.fold(init=0.0, (total, phase) => total + phase.delta_v_km_s)
}
///|
pub fn scenario_resource_cost(scenario : MissionScenario) -> Double {
scenario.phases.fold(init=0.0, (total, phase) => total + phase.resource_cost)
}
///|
pub fn scenario_is_well_formed(scenario : MissionScenario) -> Bool {
scenario.name.length() > 0 &&
scenario.phases.all(phase_is_well_formed) &&
count_phase_overlaps(scenario) == 0
}
///|
pub fn scenario_resource_margin(
scenario : MissionScenario,
resource_limit : Double,
) -> Double {
resource_limit.max(0.0) +
scenario.reserve_km_s -
scenario_resource_cost(scenario)
}
///|
pub fn summarize_scenario(
scenario : MissionScenario,
resource_limit : Double,
) -> ScenarioSummary {
let overlaps = count_phase_overlaps(scenario)
let valid = scenario_is_well_formed(scenario) &&
scenario_resource_margin(scenario, resource_limit) >= 0.0
{
valid,
phase_count: scenario.phases.length(),
total_duration_s: scenario_duration_s(scenario),
total_delta_v_km_s: scenario_delta_v(scenario),
total_resource_cost: scenario_resource_cost(scenario),
overlap_count: overlaps,
resource_remaining: scenario_resource_margin(scenario, resource_limit),
}
}
///|
pub fn phases_at_time(
scenario : MissionScenario,
time_s : Double,
) -> Array[MissionPhase] {
scenario.phases.filter(phase => phase_contains(phase, time_s))
}
///|
pub fn phases_with_priority(
scenario : MissionScenario,
minimum_priority : Int,
) -> Array[MissionPhase] {
scenario.phases.filter(phase => phase.priority >= minimum_priority)
}
///|
pub fn scenario_to_timeline(scenario : MissionScenario) -> MissionTimeline {
let ordered = scenario.sort_by_start()
let mut timeline = MissionTimeline::new()
for phase in ordered.phases {
timeline = timeline.add(
MissionEvent::new(
phase.start_s,
PayloadOperation,
phase.name,
delta_v_km_s=phase.delta_v_km_s,
duration_s=phase_duration_s(phase),
),
)
}
timeline.sort_by_time()
}
///|
pub fn scenario_report(
scenario : MissionScenario,
resource_limit : Double,
) -> String {
let summary = summarize_scenario(scenario, resource_limit)
"valid=\{summary.valid},phases=\{summary.phase_count},duration_s=\{summary.total_duration_s},delta_v_km_s=\{summary.total_delta_v_km_s},overlaps=\{summary.overlap_count}"
}
///|
pub fn scenario_phase_names(scenario : MissionScenario) -> Array[String] {
scenario.phases.map(phase => phase.name)
}
///|
pub fn scenario_peak_priority(scenario : MissionScenario) -> Int {
scenario.phases.fold(init=0, (maximum, phase) => maximum.max(phase.priority))
}
///|
pub fn scenario_is_empty(scenario : MissionScenario) -> Bool {
scenario.phases.length() == 0
}
///|
pub fn scenario_append_all(
scenario : MissionScenario,
phases : Array[MissionPhase],
) -> MissionScenario {
let mut result = scenario
for phase in phases {
result = result.add_phase(phase)
}
result
}
///|
pub fn phase_utilization(
phase : MissionPhase,
resource_limit : Double,
) -> Double {
if resource_limit <= 0.0 {
0.0
} else {
phase.resource_cost / resource_limit
}
}
///|
pub fn scenario_utilization(
scenario : MissionScenario,
resource_limit : Double,
) -> Double {
if resource_limit <= 0.0 {
0.0
} else {
scenario_resource_cost(scenario) / resource_limit
}
}