From 52e4f46a571a06a9628362797e592d632e5cf62f Mon Sep 17 00:00:00 2001 From: Wyatt Giroux Date: Tue, 9 Dec 2025 16:29:04 -0500 Subject: [PATCH 1/6] removed placeholder trajectories --- src/AEIC/trajectories/builders/ads_b.py | 42 ---------------------- src/AEIC/trajectories/builders/dymos.py | 45 ------------------------ src/AEIC/trajectories/builders/tasopt.py | 44 ----------------------- 3 files changed, 131 deletions(-) delete mode 100644 src/AEIC/trajectories/builders/ads_b.py delete mode 100644 src/AEIC/trajectories/builders/dymos.py delete mode 100644 src/AEIC/trajectories/builders/tasopt.py diff --git a/src/AEIC/trajectories/builders/ads_b.py b/src/AEIC/trajectories/builders/ads_b.py deleted file mode 100644 index e5fd2c75..00000000 --- a/src/AEIC/trajectories/builders/ads_b.py +++ /dev/null @@ -1,42 +0,0 @@ -from dataclasses import dataclass - -from AEIC.missions import Mission -from AEIC.performance_model import PerformanceModel - -from .. import Trajectory -from .base import Builder, Context, Options - - -@dataclass -class ADSBOptions: ... - - -class ADSBContext(Context): - def __init__( - self, - builder: 'ADSBBuilder', - ac_performance: PerformanceModel, - mission: Mission, - starting_mass: float | None, - ): - raise NotImplementedError('ADSBContext is not yet implemented.') - - -class ADSBBuilder(Builder): - """Model for determining flight trajectories using ADS-B data.""" - - CONTEXT_CLASS = ADSBContext - - def __init__( - self, options: Options = Options(), tasopt_options: ADSBOptions = ADSBOptions() - ): - raise NotImplementedError('ADSBBuilder is not yet implemented.') - super().__init__(options) - - def calc_starting_mass(self, **kwargs) -> float: ... - - def climb(self, traj: Trajectory, **kwargs) -> None: ... - - def cruise(self, traj: Trajectory, **kwargs) -> None: ... - - def descent(self, traj: Trajectory, **kwargs) -> None: ... diff --git a/src/AEIC/trajectories/builders/dymos.py b/src/AEIC/trajectories/builders/dymos.py deleted file mode 100644 index 77c0ddc7..00000000 --- a/src/AEIC/trajectories/builders/dymos.py +++ /dev/null @@ -1,45 +0,0 @@ -from dataclasses import dataclass - -from AEIC.missions import Mission -from AEIC.performance_model import PerformanceModel - -from .. import Trajectory -from .base import Builder, Context, Options - - -@dataclass -class DymosOptions: ... - - -class DymosContext(Context): - def __init__( - self, - builder: 'DymosBuilder', - ac_performance: PerformanceModel, - mission: Mission, - starting_mass: float | None, - ): - raise NotImplementedError('DymosContext is not yet implemented.') - - -class DymosBuilder(Builder): - """Model for determining flight trajectories using ADS-B flight data. Can - be optimized using methods defined by Marek Travnik.""" - - CONTEXT_CLASS = DymosContext - - def __init__( - self, - options: Options = Options(), - tasopt_options: DymosOptions = DymosOptions(), - ): - raise NotImplementedError('DymosBuilder is not yet implemented.') - super().__init__(options) - - def calc_starting_mass(self, **kwargs) -> float: ... - - def climb(self, traj: Trajectory, **kwargs) -> None: ... - - def cruise(self, traj: Trajectory, **kwargs) -> None: ... - - def descent(self, traj: Trajectory, **kwargs) -> None: ... diff --git a/src/AEIC/trajectories/builders/tasopt.py b/src/AEIC/trajectories/builders/tasopt.py deleted file mode 100644 index 82fe9657..00000000 --- a/src/AEIC/trajectories/builders/tasopt.py +++ /dev/null @@ -1,44 +0,0 @@ -from dataclasses import dataclass - -from AEIC.missions import Mission -from AEIC.performance_model import PerformanceModel - -from .. import Trajectory -from .base import Builder, Context, Options - - -@dataclass -class TASOPTOptions: ... - - -class TASOPTContext(Context): - def __init__( - self, - builder: 'TASOPTBuilder', - ac_performance: PerformanceModel, - mission: Mission, - starting_mass: float | None, - ): - raise NotImplementedError('TASOPTContext is not yet implemented.') - - -class TASOPTBuilder(Builder): - """Model for determining flight trajectories using TASOPT.""" - - CONTEXT_CLASS = TASOPTContext - - def __init__( - self, - options: Options = Options(), - tasopt_options: TASOPTOptions = TASOPTOptions(), - ): - raise NotImplementedError('TASOPTBuilder is not yet implemented.') - super().__init__(options) - - def calc_starting_mass(self, **kwargs) -> float: ... - - def climb(self, traj: Trajectory, **kwargs) -> None: ... - - def cruise(self, traj: Trajectory, **kwargs) -> None: ... - - def descent(self, traj: Trajectory, **kwargs) -> None: ... From 0319958c7ad22bcf5609127ff1f01b3679b4d1d5 Mon Sep 17 00:00:00 2001 From: Wyatt Giroux Date: Tue, 9 Dec 2025 17:00:36 -0500 Subject: [PATCH 2/6] created initial options and context for smart trajectory --- src/AEIC/trajectories/builders/smart.py | 153 ++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 src/AEIC/trajectories/builders/smart.py diff --git a/src/AEIC/trajectories/builders/smart.py b/src/AEIC/trajectories/builders/smart.py new file mode 100644 index 00000000..e05f27ee --- /dev/null +++ b/src/AEIC/trajectories/builders/smart.py @@ -0,0 +1,153 @@ +from dataclasses import dataclass + +from AEIC.missions import Mission +from AEIC.performance_model import PerformanceModel +from AEIC.trajectories import FlightPhase, GroundTrack, Trajectory +from AEIC.utils.files import file_location +from AEIC.utils.units import ( + FEET_TO_METERS, +) +from AEIC.weather.weather import Weather + +from .base import Builder, Context, Options + + +@dataclass +class SmartOptions: + """Additional options for the smart trajectory builder.""" + + phases: dict[FlightPhase, int] + """Flight phases and the number of points per phase to be simulated.""" + + fuel_LHV: float = 43.8e6 # J/kg + """Lower heating value of the fuel used.""" + + climb_type: str = 'max_roc' + """Solution method used to perform climb.""" + + cruise_type: str = 'const_CL' + """Solution method used to perform cruise.""" + + descent_type: str = 'const_TAS' + """Solution method used to perform descent.""" + + +class SmartContext(Context): + """Context for smart trajectory builder.""" + + def __init__( + self, + builder: 'SmartBuilder', + ac_performance: PerformanceModel, + mission: Mission, + starting_mass: float | None, + ): + # The context constructor calculates all of the fixed information used + # throughout the simulation by the trajectory builder. + self.options = builder.options + self.smart_options = builder.smart_options + + # Generate great circle ground track between departure and arrival + # locations. + ground_track = GroundTrack.great_circle( + mission.origin_position.location, mission.destination_position.location + ) + + # Climb defined as starting 3000' above airport. + self.clm_start_altitude = ( + mission.origin_position.altitude + 3000.0 * FEET_TO_METERS + ) + + # Maximum altitude in meters. + max_alt: float = ( + ac_performance.model_info['General_Information']['max_alt_ft'] + * FEET_TO_METERS + ) + + # If starting altitude is above operating ceiling, set start altitude + # to departure airport altitude. + if self.clm_start_altitude >= max_alt: + self.clm_start_altitude = mission.origin_position.altitude + + # Set descent altitude based on 3000' above arrival airport altitude; + # clamp to aircraft operating ceiling if needed. + self.des_end_altitude = ( + mission.destination_position.altitude + 3000.0 * FEET_TO_METERS + ) + if self.des_end_altitude >= max_alt: + self.des_end_altitude = max_alt + + # Determine whether takeoff procedures are being simulated; + # initial altitude is origin altitude if so, otherwise clm_start_altitude + phases = self.smart_options.phases + takeoff_phases = [ + FlightPhase.IDLE_ORIGIN, + FlightPhase.TAXI_ORIGIN, + FlightPhase.TAKEOFF + ] + if any([phase in self.phases for phase in takeoff_phases]): + initial_altitude = mission.origin_position.altitude + else: + initial_altitude = self.clm_start_altitude + + # Initialize weather regridding when requested. + self.weather: Weather | None = None + if self.options.use_weather: + mission_date = mission.departure.strftime('%Y%m%d') + weather_path = file_location( + f"{ac_performance.config.weather_data_dir}/{mission_date}.nc" + ) + self.weather = Weather( + weather_data_path=weather_path, + mission=mission, + ground_track=ground_track, + ) + + # Pass information to base context class constructor. + super().__init__( + builder, + ac_performance, + mission, + ground_track, + npoints=phases, + initial_altitude=initial_altitude, + starting_mass=starting_mass, + ) + + +class SmartBuilder(Builder): + """Model for determining flight trajectories using the a 'smart' method. + + Args: + options (Options): Base options for trajectory building. + legacy_options (LegactyOptions): Builder-specific options for legacy + trajectory builder. + """ + + CONTEXT_CLASS = SmartContext + + def __init__( + self, + options: Options = Options(), + smart_options: SmartOptions = SmartOptions(), + *args, + **kwargs, + ): + super().__init__(options, *args, **kwargs) + self.smart_options = smart_options + + def calc_starting_mass(self, **kwargs) -> float: + """""" + pass + + def fly_climb(self, traj: Trajectory, **kwargs) -> None: + """""" + pass + + def fly_cruise(self, traj: Trajectory, **kwargs): + """""" + pass + + def fly_descent(self, traj: Trajectory, **kwargs): + """""" + pass From 76ddac53822fc6d30ddca916721120ffe26f4f80 Mon Sep 17 00:00:00 2001 From: Wyatt Giroux Date: Tue, 9 Dec 2025 17:07:06 -0500 Subject: [PATCH 3/6] removed lingering references to placeholder traj types --- src/AEIC/trajectories/builders/__init__.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/AEIC/trajectories/builders/__init__.py b/src/AEIC/trajectories/builders/__init__.py index 61ca460f..f9997a9f 100644 --- a/src/AEIC/trajectories/builders/__init__.py +++ b/src/AEIC/trajectories/builders/__init__.py @@ -1,12 +1,8 @@ # Redundant aliasing to suppress Ruff unused-import messages. -from .ads_b import ADSBBuilder as ADSBBuilder -from .ads_b import ADSBOptions as ADSBOptions from .base import Builder as Builder from .base import Context as Context from .base import Options as Options -from .dymos import DymosBuilder as DymosBuilder -from .dymos import DymosOptions as DymosOptions from .legacy import LegacyBuilder as LegacyBuilder from .legacy import LegacyOptions as LegacyOptions -from .tasopt import TASOPTBuilder as TASOPTBuilder -from .tasopt import TASOPTOptions as TASOPTOptions +from .smart import SmartBuilder as SmartBuilder +from .smart import SmartOptions as SmartOptions From c29b5d9a9add4320dfb57f064a5cdfa928822fb4 Mon Sep 17 00:00:00 2001 From: Wyatt Giroux Date: Tue, 9 Dec 2025 17:08:15 -0500 Subject: [PATCH 4/6] ruff fixes --- src/AEIC/trajectories/builders/smart.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/AEIC/trajectories/builders/smart.py b/src/AEIC/trajectories/builders/smart.py index e05f27ee..82c6afdc 100644 --- a/src/AEIC/trajectories/builders/smart.py +++ b/src/AEIC/trajectories/builders/smart.py @@ -19,7 +19,7 @@ class SmartOptions: phases: dict[FlightPhase, int] """Flight phases and the number of points per phase to be simulated.""" - fuel_LHV: float = 43.8e6 # J/kg + fuel_LHV: float = 43.8e6 # J/kg """Lower heating value of the fuel used.""" climb_type: str = 'max_roc' @@ -81,10 +81,10 @@ def __init__( # initial altitude is origin altitude if so, otherwise clm_start_altitude phases = self.smart_options.phases takeoff_phases = [ - FlightPhase.IDLE_ORIGIN, - FlightPhase.TAXI_ORIGIN, - FlightPhase.TAKEOFF - ] + FlightPhase.IDLE_ORIGIN, + FlightPhase.TAXI_ORIGIN, + FlightPhase.TAKEOFF, + ] if any([phase in self.phases for phase in takeoff_phases]): initial_altitude = mission.origin_position.altitude else: From b07f30d2cef7c6b54731eb3f28f4286970e72fd9 Mon Sep 17 00:00:00 2001 From: Wyatt Giroux Date: Tue, 9 Dec 2025 17:10:52 -0500 Subject: [PATCH 5/6] added default value for SmartOptions.phases --- src/AEIC/trajectories/builders/smart.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/AEIC/trajectories/builders/smart.py b/src/AEIC/trajectories/builders/smart.py index 82c6afdc..9151a1d0 100644 --- a/src/AEIC/trajectories/builders/smart.py +++ b/src/AEIC/trajectories/builders/smart.py @@ -16,7 +16,11 @@ class SmartOptions: """Additional options for the smart trajectory builder.""" - phases: dict[FlightPhase, int] + phases: dict[FlightPhase, int] = { + FlightPhase.CLIMB: 10, + FlightPhase.CRUISE: 10, + FlightPhase.DESCENT: 10, + } """Flight phases and the number of points per phase to be simulated.""" fuel_LHV: float = 43.8e6 # J/kg From 4985a9d088453e4750fb01319fa3b649ba705508 Mon Sep 17 00:00:00 2001 From: Wyatt Giroux Date: Tue, 9 Dec 2025 17:20:10 -0500 Subject: [PATCH 6/6] reformatted phases default dictionary to be non-mutable --- src/AEIC/trajectories/builders/smart.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/AEIC/trajectories/builders/smart.py b/src/AEIC/trajectories/builders/smart.py index 9151a1d0..e3dd6b09 100644 --- a/src/AEIC/trajectories/builders/smart.py +++ b/src/AEIC/trajectories/builders/smart.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from AEIC.missions import Mission from AEIC.performance_model import PerformanceModel @@ -16,11 +16,13 @@ class SmartOptions: """Additional options for the smart trajectory builder.""" - phases: dict[FlightPhase, int] = { - FlightPhase.CLIMB: 10, - FlightPhase.CRUISE: 10, - FlightPhase.DESCENT: 10, - } + phases: dict[FlightPhase, int] = field( + default_factory=lambda: { + FlightPhase.CLIMB: 10, + FlightPhase.CRUISE: 10, + FlightPhase.DESCENT: 10, + } + ) """Flight phases and the number of points per phase to be simulated.""" fuel_LHV: float = 43.8e6 # J/kg