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 == "constant": next_point.true_airspeed = previous.true_airspeed elif self.target.equivalent_airspeed == "constant": next_point.equivalent_airspeed = start.equivalent_airspeed elif self.target.mach == "constant": 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 are given by :attr:`FlightPoint.labels` """ start = FlightPoint(start) 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 """ 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