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_from(self, start: FlightPoint) -> pd.DataFrame: """ Computes the flight path segment from provided start point. Computation ends when target is attained, or if the computation stops getting closer to target. For instance, a climb computation with too low thrust will only return one flight point, that is the provided start point. :param start: the initial flight point, defined for `altitude`, `mass` and speed (`true_airspeed`, `equivalent_airspeed` or `mach`). Can also be defined for `time` and/or `ground_distance`. :return: a pandas DataFrame where columns names match fields of :meth:`~fastoad.model_base.flight_point.FlightPoint` """ if start.time is None: start.time = 0.0 if start.ground_distance is None: start.ground_distance = 0.0 self.complete_flight_point(start) flight_points = [start] previous_point_to_target = self._get_distance_to_target(flight_points) tol = 1.0e-5 # Such accuracy is not needed, but ensures reproducibility of results. while np.abs(previous_point_to_target) > tol: self._add_new_flight_point(flight_points, self.time_step) last_point_to_target = self._get_distance_to_target(flight_points) if last_point_to_target * previous_point_to_target < 0.0: # Target has been exceeded. Let's look for the exact time step using root_scalar. def replace_last_point(time_step): """ Replaces last point of flight_points. :param time_step: time step for new point :return: new distance to target """ if isinstance(time_step, np.ndarray): # root_scalar() will provide time_step ad (1,) array, resulting # in all parameters of the new flight point being also (1,) arrays. # We want to avoid that time_step = time_step.item() del flight_points[-1] self._add_new_flight_point(flight_points, time_step) return self._get_distance_to_target(flight_points) root_scalar(replace_last_point, x0=self.time_step, x1=self.time_step / 2.0, rtol=tol) last_point_to_target = self._get_distance_to_target( flight_points) elif (np.abs(last_point_to_target) > np.abs(previous_point_to_target) # If self.target.CL is defined, it means that we look for an optimal altitude and # that target altitude can move, so it would be normal to get further from target. and self.interrupt_if_getting_further_from_target): # We get further from target. Let's stop without this point. _LOGGER.warning( 'Target cannot be reached in "%s". Segment computation interrupted.' "Please review the segment settings, especially thrust_rate.", self.name, ) del flight_points[-1] break msg = self._check_values(flight_points[-1]) if msg: _LOGGER.warning( msg + ' Segment computation interrupted in "%s".', self.name) break previous_point_to_target = last_point_to_target flight_points_df = pd.DataFrame(flight_points) return flight_points_df