def compute_from(self, start: FlightPoint) -> pd.DataFrame: self.complete_flight_point( start) # needed to ensure all speed values are computed. if self.target.altitude is not None: if isinstance(self.target.altitude, str): # Target altitude will be modified along the process, so we keep track # of the original order in target CL, that is not used otherwise. self.target.CL = self.target.altitude # pylint: disable=invalid-name # let's put a numerical, negative value in self.target.altitude to # ensure there will be no problem in self._get_distance_to_target() self.target.altitude = -1000.0 self.interrupt_if_getting_further_from_target = False else: # Target altitude is fixed, back to original settings (in case # this instance is used more than once) self.target.CL = None self.interrupt_if_getting_further_from_target = True atm = AtmosphereSI(start.altitude) if self.target.equivalent_airspeed == self.CONSTANT_VALUE: atm.equivalent_airspeed = start.equivalent_airspeed start.true_airspeed = atm.true_airspeed elif self.target.mach == self.CONSTANT_VALUE: atm.mach = start.mach start.true_airspeed = atm.true_airspeed return super().compute_from(start)
def test_EngineSet(): """Tests behaviour of FuelEngineSet""" engine = DummyEngine(1.2e5, 1.0e-5) # input = thrust rate thrust_rate = np.linspace(0.0, 1.0, 20) for engine_count in [1, 2, 3, 4]: engine_set = FuelEngineSet(engine, engine_count) flight_point = FlightPoint(mach=0.0, altitude=0.0, engine_setting=1, thrust_rate=thrust_rate) engine_set.compute_flight_points(flight_point) assert_allclose(flight_point.sfc, engine.max_sfc * thrust_rate) assert_allclose(flight_point.thrust, engine.max_thrust * engine_count * thrust_rate) # input = thrust thrust = np.linspace(0.0, engine.max_thrust, 30) for engine_count in [1, 2, 3, 4]: engine_set = FuelEngineSet(engine, engine_count) flight_point = FlightPoint(mach=0.0, altitude=0.0, engine_setting=1, thrust=thrust) engine_set.compute_flight_points(flight_point) assert_allclose(flight_point.thrust_rate, thrust / engine_count / engine.max_thrust) assert_allclose(flight_point.sfc, engine.max_sfc * flight_point.thrust_rate)
def _climb_to_altitude_and_cruise( self, start: FlightPoint, cruise_altitude: float, climb_segment: AltitudeChangeSegment, cruise_segment: CruiseSegment, ): """ Climbs up to cruise_altitude and cruise, while ensuring final ground_distance is equal to self.target.ground_distance. :param start: :param cruise_altitude: :param climb_segment: :param cruise_segment: :return: """ climb_segment.target = FlightPoint( altitude=cruise_altitude, mach=cruise_segment.target.mach, true_airspeed=cruise_segment.target.true_airspeed, equivalent_airspeed=cruise_segment.target.equivalent_airspeed, ) climb_points = climb_segment.compute_from(start) cruise_start = FlightPoint.create(climb_points.iloc[-1]) cruise_segment.target.ground_distance = (self.target.ground_distance - cruise_start.ground_distance) cruise_points = cruise_segment.compute_from(cruise_start) return pd.concat([climb_points, cruise_points]).reset_index(drop=True)
def complete_flight_point(self, flight_point: FlightPoint): """ Computes data for provided flight point. Assumes that it is already defined for time, altitude, mass, ground distance and speed (TAS, EAS, or Mach). :param flight_point: the flight point that will be completed in-place """ flight_point.engine_setting = self.engine_setting self._complete_speed_values(flight_point) atm = AtmosphereSI(flight_point.altitude) reference_force = 0.5 * atm.density * flight_point.true_airspeed**2 * self.reference_area if self.polar: flight_point.CL = flight_point.mass * g / reference_force flight_point.CD = self.polar.cd(flight_point.CL) else: flight_point.CL = flight_point.CD = 0.0 flight_point.drag = flight_point.CD * reference_force self._compute_propulsion(flight_point) flight_point.slope_angle, flight_point.acceleration = self._get_gamma_and_acceleration( flight_point.mass, flight_point.drag, flight_point.thrust)
def test_breguet_cruise(polar): propulsion = FuelEngineSet(DummyEngine(0.5e5, 1.0e-5), 2) segment = BreguetCruiseSegment( target=FlightPoint(ground_distance=5.0e5), propulsion=propulsion, reference_area=120.0, polar=polar, engine_setting=EngineSetting. CRUISE, # The engine model does not use this setting ) flight_points = segment.compute_from( FlightPoint(time=10000.0, mass=70000.0, altitude=10000.0, mach=0.78)) print_dataframe(flight_points) first_point = flight_points.iloc[0] last_point = flight_points.iloc[-1] # Note: reference values are obtained by running the process with CruiseSegment and 0.05s as # time step assert_allclose(first_point.altitude, 10000.0) assert_allclose(first_point.true_airspeed, 233.6, atol=0.1) assert first_point.engine_setting == EngineSetting.CRUISE assert_allclose(last_point.ground_distance, 500000.0) assert_allclose(last_point.altitude, 10000.0) assert_allclose(last_point.time, 12141.0, rtol=1e-2) assert_allclose(last_point.true_airspeed, 233.6, atol=0.1) assert_allclose(last_point.mass, 69568.0, rtol=1e-4) assert last_point.engine_setting == EngineSetting.CRUISE
def test_climb_fixed_altitude_at_constant_EAS(polar): propulsion = FuelEngineSet(DummyEngine(1.0e5, 1.0e-5), 2) flight_points = AltitudeChangeSegment( target=FlightPoint(altitude=10000.0, equivalent_airspeed="constant"), propulsion=propulsion, reference_area=120.0, polar=polar, thrust_rate=1.0, time_step=2.0, engine_setting=EngineSetting. CRUISE, # The engine model does not use this setting ).compute_from( FlightPoint(altitude=5000.0, mass=70000.0, equivalent_airspeed=100.0)) first_point = flight_points.iloc[0] last_point = flight_points.iloc[-1] # Note: reference values are obtained by running the process with 0.01s as time step assert_allclose(last_point.altitude, 10000.0) assert_allclose(last_point.equivalent_airspeed, 100.0) assert_allclose(last_point.time, 145.2, rtol=1e-2) assert_allclose(first_point.true_airspeed, 129.0, atol=0.1) assert_allclose(last_point.true_airspeed, 172.3, atol=0.1) assert_allclose(last_point.mass, 69710.0, rtol=1e-4) assert_allclose(last_point.ground_distance, 20915.0, rtol=1e-3) assert last_point.engine_setting == EngineSetting.CRUISE
def _compute_breguet(self, inputs, outputs): """ Computes mission using simple Breguet formula at altitude==100m and Mach 0.1 (but max L/D ratio is assumed anyway) Useful only for initiating the computation. :param inputs: OpenMDAO input vector :param outputs: OpenMDAO output vector """ propulsion_model = FuelEngineSet( self._engine_wrapper.get_model(inputs), inputs["data:geometry:propulsion:engine:count"] ) high_speed_polar = self._get_initial_polar(inputs) distance = np.asscalar( np.sum(self._mission_wrapper.get_route_ranges(inputs, self.options["mission_name"])) ) altitude = 100.0 cruise_mach = 0.1 breguet = BreguetCruiseSegment( FlightPoint(ground_distance=distance), propulsion=propulsion_model, polar=high_speed_polar, use_max_lift_drag_ratio=True, ) start_point = FlightPoint( mass=inputs[self._mission_vars.TOW.value], altitude=altitude, mach=cruise_mach ) flight_points = breguet.compute_from(start_point) end_point = FlightPoint.create(flight_points.iloc[-1]) outputs[self._mission_vars.NEEDED_BLOCK_FUEL.value] = start_point.mass - end_point.mass
def test_optimal_cruise(polar): propulsion = FuelEngineSet(DummyEngine(0.5e5, 1.0e-5), 2) segment = OptimalCruiseSegment( target=FlightPoint(ground_distance=5.0e5), propulsion=propulsion, reference_area=120.0, polar=polar, engine_setting=EngineSetting.CRUISE, ) flight_points = segment.compute_from( FlightPoint(mass=70000.0, time=1000.0, ground_distance=1e5, mach=0.78)) print_dataframe(flight_points) first_point = flight_points.iloc[0] last_point = flight_points.iloc[-1] # Note: reference values are obtained by running the process with 0.05s as time step assert_allclose(first_point.altitude, 9156.0, atol=1.0) assert_allclose(first_point.true_airspeed, 236.4, atol=0.1) assert_allclose(first_point.CL, polar.optimal_cl) assert_allclose(first_point.CL, polar.optimal_cl) assert first_point.engine_setting == EngineSetting.CRUISE assert_allclose(last_point.ground_distance, 600000.0) assert_allclose(last_point.CL, polar.optimal_cl) assert_allclose(last_point.altitude, 9196.0, atol=1.0) assert_allclose(last_point.time, 3115.0, rtol=1e-2) assert_allclose(last_point.true_airspeed, 236.3, atol=0.1) assert_allclose(last_point.mass, 69577.0, rtol=1e-4) assert last_point.engine_setting == EngineSetting.CRUISE
def test_climb_fixed_altitude_at_constant_TAS(polar): propulsion = FuelEngineSet(DummyEngine(1.0e5, 1.0e-5), 2) # initialisation then change instance attributes segment = AltitudeChangeSegment( target=FlightPoint(altitude=10000.0), propulsion=propulsion, reference_area=120.0, polar=polar, engine_setting=EngineSetting.CLIMB, ) # not constant TAS order, as it is the default segment.thrust_rate = 1.0 segment.time_step = 2.0 flight_points = segment.compute_from( FlightPoint(altitude=5000.0, mass=70000.0, true_airspeed=150.0)) # Test with dict last_point = flight_points.iloc[-1] # Note: reference values are obtained by running the process with 0.01s as time step assert_allclose(last_point.altitude, 10000.0) assert_allclose(last_point.true_airspeed, 150.0) assert_allclose(last_point.time, 143.5, rtol=1e-2) assert_allclose(last_point.mass, 69713.0, rtol=1e-4) assert_allclose(last_point.ground_distance, 20943.0, rtol=1e-3) assert last_point.engine_setting == EngineSetting.CLIMB
def compute_next_flight_point(self, flight_points: List[FlightPoint], time_step: float) -> FlightPoint: """ Computes time, altitude, speed, mass and ground distance of next flight point. :param flight_points: previous flight points :param time_step: time step for computing next point :return: the computed next flight point """ start = flight_points[0] previous = flight_points[-1] next_point = FlightPoint() next_point.mass = previous.mass - self.propulsion.get_consumed_mass( previous, time_step) next_point.time = previous.time + time_step next_point.ground_distance = ( previous.ground_distance + previous.true_airspeed * time_step * np.cos(previous.slope_angle)) self._compute_next_altitude(next_point, previous) if self.target.true_airspeed == self.CONSTANT_VALUE: next_point.true_airspeed = previous.true_airspeed elif self.target.equivalent_airspeed == self.CONSTANT_VALUE: next_point.equivalent_airspeed = start.equivalent_airspeed elif self.target.mach == self.CONSTANT_VALUE: next_point.mach = start.mach else: next_point.true_airspeed = previous.true_airspeed + time_step * previous.acceleration # The naming is not done in complete_flight_point for not naming the start point next_point.name = self.name return next_point
def compute(self, inputs: Vector, outputs: Vector, start_flight_point: FlightPoint) -> pd.DataFrame: """ To be used during compute() of an OpenMDAO component. Builds the mission from input file, and computes it. `outputs` vector is filled with duration, burned fuel and covered ground distance for each part of the flight. :param inputs: the input vector of the OpenMDAO component :param outputs: the output vector of the OpenMDAO component :param start_flight_point: the starting flight point just after takeoff :return: a pandas DataFrame where columns names match fields of :class:`~fastoad.model_base.flight_point.FlightPoint` """ mission = self.build(inputs, self.mission_name) def _compute_vars(name_root, start: FlightPoint, end: FlightPoint): """Computes duration, burned fuel and covered distance.""" if name_root + ":duration" in outputs: outputs[name_root + ":duration"] = end.time - start.time if name_root + ":fuel" in outputs: outputs[name_root + ":fuel"] = start.mass - end.mass if name_root + ":distance" in outputs: outputs[ name_root + ":distance"] = end.ground_distance - start.ground_distance if not start_flight_point.name: start_flight_point.name = mission.flight_sequence[0].name current_flight_point = start_flight_point flight_points = mission.compute_from(start_flight_point) for part in mission.flight_sequence: var_name_root = "data:mission:%s" % part.name part_end = FlightPoint.create( flight_points.loc[flight_points.name.str.startswith( part.name)].iloc[-1]) _compute_vars(var_name_root, current_flight_point, part_end) if isinstance(part, FlightSequence): # In case of a route, outputs are computed for each phase in the route phase_start = current_flight_point for phase in part.flight_sequence: phase_points = flight_points.loc[flight_points.name == phase.name] if len(phase_points) > 0: phase_end = FlightPoint.create(phase_points.iloc[-1]) var_name_root = "data:mission:%s" % phase.name _compute_vars(var_name_root, phase_start, phase_end) phase_start = phase_end current_flight_point = part_end # Outputs for the whole mission var_name_root = "data:mission:%s" % mission.name _compute_vars(var_name_root, start_flight_point, current_flight_point) return flight_points
def compute_flight_points(self, flight_point: FlightPoint): if flight_point.thrust_is_regulated or flight_point.thrust_rate is None: flight_point.thrust_rate = flight_point.thrust / self.max_thrust else: flight_point.thrust = self.max_thrust * flight_point.thrust_rate flight_point.sfc = self.max_sfc * (1.0 + flight_point.thrust_rate) / 2.0
def test_compute_flight_points(): engine = RubberEngine(5, 30, 1500, 1, 0.95, 10000) # f0=1 so that output is simply fc/f0 # Test with scalars flight_point = FlightPoint( mach=0, altitude=0, engine_setting=EngineSetting.TAKEOFF, thrust_rate=0.8) # with engine_setting as EngineSetting engine.compute_flight_points(flight_point) np.testing.assert_allclose(flight_point.thrust, 0.9553 * 0.8, rtol=1e-4) np.testing.assert_allclose(flight_point.sfc, 0.99210e-5, rtol=1e-4) flight_point = FlightPoint(mach=0.3, altitude=0, engine_setting=EngineSetting.CLIMB.value, thrust=0.35677) # with engine_setting as int engine.compute_flight_points(flight_point) np.testing.assert_allclose(flight_point.thrust_rate, 0.5, rtol=1e-4) np.testing.assert_allclose(flight_point.sfc, 1.3496e-5, rtol=1e-4) # Test full arrays # 2D arrays are used, where first line is for thrust rates, and second line # is for thrust values. # As thrust rates and thrust values match, thrust rate results are 2 equal # lines and so are thrust value results. machs = [0, 0.3, 0.3, 0.8, 0.8] altitudes = [0, 0, 0, 10000, 13000] thrust_rates = [0.8, 0.5, 0.5, 0.4, 0.7] thrusts = [0.9553 * 0.8, 0.38851, 0.35677, 0.09680, 0.11273] engine_settings = [ EngineSetting.TAKEOFF, EngineSetting.TAKEOFF, EngineSetting.CLIMB, EngineSetting.IDLE, EngineSetting.CRUISE.value, ] # mix EngineSetting with integers expected_sfc = [0.99210e-5, 1.3496e-5, 1.3496e-5, 1.8386e-5, 1.5957e-5] flight_points = pd.DataFrame() flight_points["mach"] = machs + machs flight_points["altitude"] = altitudes + altitudes flight_points["engine_setting"] = engine_settings + engine_settings flight_points["thrust_is_regulated"] = [False] * 5 + [True] * 5 flight_points["thrust_rate"] = thrust_rates + [0.0] * 5 flight_points["thrust"] = [0.0] * 5 + thrusts engine.compute_flight_points(flight_points) np.testing.assert_allclose(flight_points.sfc, expected_sfc + expected_sfc, rtol=1e-4) np.testing.assert_allclose(flight_points.thrust_rate, thrust_rates + thrust_rates, rtol=1e-4) np.testing.assert_allclose(flight_points.thrust, thrusts + thrusts, rtol=1e-4)
def test_dummy_reserve(): dummy_reserve = DummyTransitionSegment( target=FlightPoint(altitude=0.0, mach=0.0), reserve_mass_ratio=0.1 ) flight_points = dummy_reserve.compute_from(FlightPoint(altitude=0.0, mach=0.0, mass=55.0e3)) assert_allclose(flight_points.mass, [55.0e3, 55.0e3, 50.0e3]) assert_allclose(flight_points.altitude, [0.0, 0.0, 0.0]) assert_allclose(flight_points.ground_distance, [0.0, 0.0, 0.0]) assert_allclose(flight_points.mach, [0.0, 0.0, 0.0]) assert_allclose(flight_points.true_airspeed, [0.0, 0.0, 0.0], rtol=1.0e-4)
def test_ranged_route(low_speed_polar, high_speed_polar, cleanup): engine = DummyEngine(1e5, 2.0e-5) propulsion = FuelEngineSet(engine, 2) total_distance = 2.0e6 kwargs = dict(propulsion=propulsion, reference_area=120.0) initial_climb = InitialClimbPhase(**kwargs, polar=low_speed_polar, thrust_rate=1.0, name="initial_climb", time_step=0.2) climb = ClimbPhase( **kwargs, polar=high_speed_polar, thrust_rate=0.8, target_altitude="mach", maximum_mach=0.78, name="climb", time_step=5.0, ) cruise = CruiseSegment( **kwargs, target=FlightPoint(ground_distance=0.0), polar=high_speed_polar, engine_setting=EngineSetting.CRUISE, name=FlightPhase.CRUISE.value, ) descent = DescentPhase( **kwargs, polar=high_speed_polar, thrust_rate=0.05, target_altitude=1500.0 * foot, name=FlightPhase.DESCENT.value, time_step=5.0, ) flight_calculator = RangedRoute([initial_climb, climb], cruise, [descent], total_distance) assert flight_calculator.cruise_speed == ("mach", 0.78) start = FlightPoint(true_airspeed=150.0 * knot, altitude=100.0 * foot, mass=70000.0, ground_distance=100000.0) flight_points = flight_calculator.compute_from(start) # plot_flight(flight_points, "test_ranged_flight.png") assert_allclose( flight_points.iloc[-1].ground_distance, total_distance + start.ground_distance, atol=flight_calculator.distance_accuracy, )
def test_acceleration_not_enough_thrust(polar): propulsion = FuelEngineSet(DummyEngine(0.5e5, 1.0e-5), 2) segment = SpeedChangeSegment( target=FlightPoint(true_airspeed=250.0), propulsion=propulsion, reference_area=120.0, polar=polar, thrust_rate=0.1, ) assert len( segment.compute_from( FlightPoint(altitude=5000.0, true_airspeed=150.0, mass=70000.0)))
def test_dummy_descent_with_reserve(): dummy_descent_reserve = DummyTransitionSegment( target=FlightPoint(altitude=0.0, mach=0.0, ground_distance=500.0e3), mass_ratio=0.9, reserve_mass_ratio=0.08, ) flight_points = dummy_descent_reserve.compute_from( FlightPoint(altitude=9.0e3, mass=60.0e3, mach=0.8)) assert_allclose(flight_points.mass, [60.0e3, 54.0e3, 50.0e3]) assert_allclose(flight_points.altitude, [9.0e3, 0.0, 0.0]) assert_allclose(flight_points.ground_distance, [0.0, 500.0e3, 500.0e3]) assert_allclose(flight_points.mach, [0.8, 0.0, 0.0]) assert_allclose(flight_points.true_airspeed, [243.04, 0.0, 0.0], rtol=1.0e-4)
def test_climb_not_enough_thrust(polar): propulsion = FuelEngineSet(DummyEngine(1.0e5, 1.0e-5), 2) segment = AltitudeChangeSegment( target=FlightPoint(altitude=10000.0, true_airspeed="constant"), propulsion=propulsion, reference_area=120.0, polar=polar, thrust_rate=0.1, ) assert (len( segment.compute_from( FlightPoint(altitude=5000.0, true_airspeed=150.0, mass=70000.0))) == 1)
def test_dummy_climb(): dummy_climb = DummyTransitionSegment( target=FlightPoint(altitude=9.0e3, mach=0.8, ground_distance=400.0e3), mass_ratio=0.8 ) flight_points = dummy_climb.compute_from( FlightPoint(altitude=0.0, mass=100.0e3, mach=0.0, ground_distance=100.0e3) ) assert_allclose(flight_points.mass, [100.0e3, 80.0e3]) assert_allclose(flight_points.altitude, [0.0, 9.0e3]) assert_allclose(flight_points.ground_distance, [100.0e3, 500.0e3]) assert_allclose(flight_points.mach, [0.0, 0.8]) assert_allclose(flight_points.true_airspeed, [0.0, 243.04], rtol=1.0e-4)
def compute_from(self, start: FlightPoint) -> pd.DataFrame: parts = [] part_start = start for part in self.flight_sequence: flight_points = part.compute_from(part_start) if len(parts) > 0 and len(flight_points) > 1: # First point of the segment is omitted, as it is the last of previous segment. # # But sometimes (especially in the case of simplistic segments), the new first # point may contain more information than the previous last one. In such case, # it is interesting to complete the previous last one. last_flight_points = parts[-1] last_index = last_flight_points.index[-1] for name in flight_points.columns: value = last_flight_points.loc[last_index, name] if not value: last_flight_points.loc[last_index, name] = flight_points.loc[0, name] parts.append(flight_points.iloc[1:]) else: # But it is kept if the computed segment is the first one. parts.append(flight_points) part_start = FlightPoint.create(flight_points.iloc[-1]) if parts: return pd.concat(parts).reset_index(drop=True)
def test_hold(polar): propulsion = FuelEngineSet(DummyEngine(0.5e5, 2.0e-5), 2) segment = HoldSegment(target=FlightPoint(time=3000.0), propulsion=propulsion, reference_area=120.0, polar=polar) flight_points = segment.compute_from( FlightPoint(altitude=500.0, equivalent_airspeed=250.0, mass=60000.0), ) last_point = flight_points.iloc[-1] assert_allclose(last_point.time, 3000.0) assert_allclose(last_point.altitude, 500.0) assert_allclose(last_point.equivalent_airspeed, 250.0, atol=0.1) assert_allclose(last_point.mass, 57776.0, rtol=1e-4) assert_allclose(last_point.ground_distance, 768323.0, rtol=1.0e-3)
def flight_sequence(self) -> List[Union[IFlightPart, str]]: return [ AltitudeChangeSegment( target=FlightPoint(equivalent_airspeed="constant", altitude=400.0 * foot), engine_setting=EngineSetting.TAKEOFF, **self.segment_kwargs, ), SpeedChangeSegment( target=FlightPoint(equivalent_airspeed=250.0 * knot), engine_setting=EngineSetting.TAKEOFF, **self.segment_kwargs, ), AltitudeChangeSegment( target=FlightPoint(equivalent_airspeed="constant", altitude=1500.0 * foot), engine_setting=EngineSetting.TAKEOFF, **self.segment_kwargs, ), ]
def test_taxi(): propulsion = FuelEngineSet(DummyEngine(0.5e5, 1.0e-5), 2) segment = TaxiSegment(target=FlightPoint(time=500.0), propulsion=propulsion, thrust_rate=0.1) flight_points = segment.compute_from( FlightPoint(altitude=10.0, true_airspeed=10.0, mass=50000.0, time=10000.0), ) last_point = flight_points.iloc[-1] assert_allclose(last_point.altitude, 10.0, atol=1.0) assert_allclose(last_point.time, 10500.0, rtol=1e-2) assert_allclose(last_point.true_airspeed, 10.0, atol=0.1) assert_allclose(last_point.mass, 49973.0, rtol=1e-4) assert_allclose(last_point.ground_distance, 5000.0)
def test_climb_and_cruise_at_optimal_flight_level(polar): propulsion = FuelEngineSet(DummyEngine(0.5e5, 3.0e-5), 2) reference_area = 120.0 segment = ClimbAndCruiseSegment( target=FlightPoint( ground_distance=10.0e6, altitude=AltitudeChangeSegment.OPTIMAL_FLIGHT_LEVEL), propulsion=propulsion, reference_area=reference_area, polar=polar, engine_setting=EngineSetting.CRUISE, climb_segment=AltitudeChangeSegment( target=FlightPoint(), propulsion=propulsion, reference_area=reference_area, polar=polar, thrust_rate=0.9, engine_setting=EngineSetting.CLIMB, ), ) flight_points = segment.compute_from( FlightPoint(mass=70000.0, altitude=8000.0, mach=0.78, ground_distance=1.0e6)) first_point = flight_points.iloc[0] last_point = flight_points.iloc[-1] # Note: reference values are obtained by running the process with 1.0s as time step assert_allclose(first_point.altitude, 8000.0) assert_allclose(first_point.thrust_rate, 0.9) assert_allclose(first_point.true_airspeed, 240.3, atol=0.1) assert first_point.engine_setting == EngineSetting.CLIMB assert_allclose(flight_points.mach, 0.78) assert_allclose(last_point.altitude, 9753.6) assert_allclose(last_point.ground_distance, 11.0e6) assert_allclose(last_point.time, 42659.0, rtol=1e-3) assert_allclose(last_point.true_airspeed, 234.4, atol=0.1) assert_allclose(last_point.mass, 48874.0, rtol=1e-4) assert last_point.engine_setting == EngineSetting.CRUISE
def test_descent_to_fixed_altitude_at_constant_EAS(polar): propulsion = FuelEngineSet(DummyEngine(1.0e5, 1.0e-5), 2) flight_points = AltitudeChangeSegment( target=FlightPoint(altitude=5000.0, equivalent_airspeed="constant"), propulsion=propulsion, reference_area=100.0, polar=polar, thrust_rate=0.1, time_step=2.0, ).compute_from(FlightPoint(altitude=10000.0, equivalent_airspeed=200.0, mass=70000.0)) last_point = flight_points.iloc[-1] # Note: reference values are obtained by running the process with 0.01s as time step assert_allclose(last_point.altitude, 5000.0) assert_allclose(last_point.equivalent_airspeed, 200.0) assert_allclose(last_point.time, 821.4, rtol=1e-2) assert_allclose(last_point.mass, 69910.0, rtol=1e-4) assert_allclose(last_point.ground_distance, 243155.0, rtol=1e-3)
def flight_sequence(self) -> List[Union[IFlightPart, str]]: self.segment_kwargs["engine_setting"] = EngineSetting.IDLE return [ AltitudeChangeSegment( target=FlightPoint(equivalent_airspeed=300.0 * knot, mach="constant"), **self.segment_kwargs, ), AltitudeChangeSegment( target=FlightPoint(altitude=10000.0 * foot, equivalent_airspeed="constant"), **self.segment_kwargs, ), SpeedChangeSegment( target=FlightPoint(equivalent_airspeed=250.0 * knot), **self.segment_kwargs, ), AltitudeChangeSegment( target=FlightPoint(altitude=self.target_altitude, equivalent_airspeed="constant"), **self.segment_kwargs, ), ]
def test_acceleration_to_mach(polar): propulsion = FuelEngineSet(DummyEngine(0.5e5, 1.0e-5), 2) flight_points = SpeedChangeSegment( target=FlightPoint(mach=0.8), propulsion=propulsion, reference_area=120.0, polar=polar, thrust_rate=1.0, time_step=0.2, ).compute_from( FlightPoint(altitude=1000.0, true_airspeed=150.0, mass=70000.0)) last_point = flight_points.iloc[-1] # Note: reference values are obtained by running the process with 0.01s as time step assert_allclose(last_point.time, 138.0, rtol=1e-2) assert_allclose(last_point.altitude, 1000.0) assert_allclose(last_point.mach, 0.8, atol=1e-5) assert_allclose(last_point.mass, 69862.0, rtol=1e-4) assert_allclose(last_point.ground_distance, 29470.0, rtol=1e-3)
def test_climb_optimal_flight_level_at_fixed_mach(polar): propulsion = FuelEngineSet(DummyEngine(1.0e5, 1.0e-5), 2) flight_points = AltitudeChangeSegment( target=FlightPoint(altitude=AltitudeChangeSegment.OPTIMAL_FLIGHT_LEVEL, mach="constant"), propulsion=propulsion, reference_area=120.0, polar=polar, thrust_rate=1.0, time_step=2.0, ).compute_from(FlightPoint(altitude=5000.0, mach=0.82, mass=70000.0)) last_point = flight_points.iloc[-1] # Note: reference values are obtained by running the process with 0.01s as time step assert_allclose(flight_points.mach, 0.82) assert_allclose(last_point.altitude / foot, 32000.0, atol=0.1) assert_allclose(last_point.time, 77.5, rtol=1e-2) assert_allclose(last_point.true_airspeed, 246.44, rtol=1e-4) assert_allclose(last_point.mass, 69843.0, rtol=1e-4) assert_allclose(last_point.ground_distance, 19179.0, rtol=1e-3)
def _compute_taxi_out(self, inputs, outputs, propulsion_model): """ Computes the taxi-out segment. """ start_of_taxi_out = FlightPoint( altitude=inputs[self._mission_vars.TAKEOFF_ALTITUDE.value], true_airspeed=0.0, # start mass is irrelevant here as long it does not get negative during computation. mass=inputs[self._mission_vars.TOW.value], ) taxi_segment = TaxiSegment( target=FlightPoint(time=inputs[self._mission_vars.TAXI_OUT_DURATION.value],), thrust_rate=inputs[self._mission_vars.TAXI_OUT_THRUST_RATE.value], propulsion=propulsion_model, ) flight_points = taxi_segment.compute_from(start_of_taxi_out) end_of_taxi_out = flight_points.iloc[-1] outputs[self._mission_vars.TAXI_OUT_FUEL.value] = ( start_of_taxi_out.mass - end_of_taxi_out.mass )
def test_descent_to_fixed_EAS_at_constant_mach(polar): propulsion = FuelEngineSet(DummyEngine(1.0e5, 1.0e-5), 2) flight_points = AltitudeChangeSegment( target=FlightPoint(equivalent_airspeed=150.0, mach="constant"), propulsion=propulsion, reference_area=100.0, polar=polar, thrust_rate=0.1, # time_step=5.0, # we use default time step ).compute_from(FlightPoint(altitude=10000.0, mass=70000.0, mach=0.78)) last_point = flight_points.iloc[-1] # Note: reference values are obtained by running the process with 0.01s as time step assert_allclose(last_point.equivalent_airspeed, 150.0) assert_allclose(last_point.mach, 0.78) assert_allclose(last_point.time, 343.6, rtol=1e-2) assert_allclose(last_point.altitude, 8654.0, atol=1.0) assert_allclose(last_point.true_airspeed, 238.1, atol=0.1) assert_allclose(last_point.mass, 69962.0, rtol=1e-4) assert_allclose(last_point.ground_distance, 81042.0, rtol=1e-3)