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 :meth:`fastoad.base.flight_point.FlightPoint.get_attribute_keys` """ 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 _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 compute_mission(self, inputs, outputs): """ Computes mission using time-step integration. :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"]) reference_area = inputs["data:geometry:wing:area"] self._mission.propulsion = propulsion_model self._mission.reference_area = reference_area end_of_takeoff = FlightPoint( time=0.0, # FIXME: legacy FAST was considering that aircraft was at MTOW before taxi out, # though it supposed to be at MTOW at takeoff. We keep this logic for sake of # non-regression, but it should be corrected later. mass=inputs["data:weight:aircraft:MTOW"] - inputs["data:mission:sizing:takeoff:fuel"] - inputs["data:mission:sizing:taxi_out:fuel"], true_airspeed=inputs["data:mission:sizing:takeoff:V2"], altitude=inputs["data:mission:sizing:takeoff:altitude"] + 35 * foot, ground_distance=0.0, ) self.flight_points = self._mission.compute(inputs, outputs, end_of_takeoff) # Final ================================================================ end_of_descent = FlightPoint.create(self.flight_points.loc[ self.flight_points.name == "sizing:main_route:descent"].iloc[-1]) end_of_taxi_in = FlightPoint.create(self.flight_points.iloc[-1]) fuel_route = inputs["data:weight:aircraft:MTOW"] - end_of_descent.mass outputs[ "data:mission:sizing:ZFW"] = end_of_taxi_in.mass - 0.03 * fuel_route outputs["data:mission:sizing:fuel"] = ( inputs["data:weight:aircraft:MTOW"] - outputs["data:mission:sizing:ZFW"]) if self.options["out_file"]: self.flight_points.to_csv(self.options["out_file"])
def _compute_mission(self, inputs, outputs): """ Computes mission using time-step integration. :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"]) reference_area = inputs["data:geometry:wing:area"] self._mission_wrapper.propulsion = propulsion_model self._mission_wrapper.reference_area = reference_area end_of_takeoff = FlightPoint( time=0.0, mass=inputs[self._mission_vars.TOW.value], true_airspeed=inputs[self._mission_vars.TAKEOFF_V2.value], altitude=inputs[self._mission_vars.TAKEOFF_ALTITUDE.value] + 35 * foot, ground_distance=0.0, ) self.flight_points = self._mission_wrapper.compute( inputs, outputs, end_of_takeoff) # Final ================================================================ end_of_mission = FlightPoint.create(self.flight_points.iloc[-1]) zfw = end_of_mission.mass - self._mission_wrapper.get_reserve( self.flight_points, self.options["mission_name"]) outputs[self._mission_vars.BLOCK_FUEL.value] = ( inputs[self._mission_vars.TOW.value] + inputs[self._mission_vars.TAKEOFF_FUEL.value] + inputs[self._mission_vars.TAXI_OUT_FUEL.value] - zfw) if self.options["is_sizing"]: outputs["data:weight:aircraft:sizing_block_fuel"] = outputs[ self._mission_vars.BLOCK_FUEL.value] def as_scalar(value): if isinstance(value, np.ndarray): return np.asscalar(value) return value self.flight_points = self.flight_points.applymap(as_scalar) if self.options["out_file"]: self.flight_points.to_csv(self.options["out_file"])
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: # First point of the segment is omitted, as it is the # last of previous segment. if len(flight_points) > 1: 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 _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.BLOCK_FUEL. value] = start_point.mass - end_point.mass