<jupyter_start><jupyter_text>Python Flight Mechanics Engine  This installs `pyfme` to be run in this online notebook  Aircraft  In order to perform a simulation, the first thing we need is an aircraft:<jupyter_code>%load_ext autoreload
%autoreload 2
from pyfme.aircrafts import Cessna172
import matplotlib.pyplot as plt
import numpy as np
aircraft = Cessna172()<jupyter_output><empty_output><jupyter_text>Aircraft will provide the simulator the forces, moments and inertial properties in order to perform the integration of the dynamic system equations:<jupyter_code>print(f"Aircraft mass: {aircraft.mass} kg")
print(f"Aircraft inertia tensor: \n {aircraft.inertia} kg/m²")<jupyter_output>Aircraft mass: 1043.2616 kg
Aircraft inertia tensor: 
 [[  1285.3154166  -27116.359      -13558.1795    ]
 [-27116.359        1824.9309607  -13558.1795    ]
 [-13558.1795     -13558.1795       2666.89390765]] kg/m²<jupyter_text>For the aircraft, in order to calculate its forces and moments it is necessary to set the controls values within the limits:<jupyter_code>print(aircraft.control_limits)<jupyter_output>{'delta_elevator': (-0.4537856055185257, 0.48869219055841229), 'delta_aileron': (-0.26179938779914941, 0.3490658503988659), 'delta_rudder': (-0.27925268031909273, 0.27925268031909273), 'delta_throttle': (0, 1)}<jupyter_text>but also to provide and environment (ie. atmosphere, winds, gravity) and the aircraft state, which will also determine the aerodynamic contribution.  Environment<jupyter_code>from pyfme.environment.atmosphere import ISA1976
from pyfme.environment.wind import NoWind
from pyfme.environment.gravity import VerticalConstant
atmosphere = ISA1976()
gravity = VerticalConstant()
wind = NoWind()<jupyter_output><empty_output><jupyter_text>The atmosphere, wind and gravity model make up the environment:<jupyter_code>from pyfme.environment import Environment
environment = Environment(atmosphere, gravity, wind)<jupyter_output><empty_output><jupyter_text>The environment has an update method which given the state (ie. position, altitude...) updates the environment variables (ie. density, wind magnitude, gravity force...)<jupyter_code>help(environment.update)<jupyter_output>Help on method update in module pyfme.environment.environment:

update(state) method of pyfme.environment.environment.Environment instance<jupyter_text>State  Even if the state can be set manually by giving the position, attitude, velocity, angular velocities... Most of the times, the user will want to trim the aircraft in a stationary condition. The aircraft controls to flight in that condition will be also provided by the trimmer.<jupyter_code>from pyfme.utils.trimmer import steady_state_trim
help(steady_state_trim)
from pyfme.models.state.position import EarthPosition
pos = EarthPosition(x=0, y=0, height=1000)
psi = 0.5  # rad
TAS = 45  # m/s
controls0 = {'delta_elevator': 0, 'delta_aileron': 0, 'delta_rudder': 0, 'delta_t': 0.5}
trimmed_state, trimmed_controls = steady_state_trim(
    aircraft,
    environment,
    pos,
    psi,
    TAS,
    controls0
)    
trimmed_state
trimmed_controls<jupyter_output><empty_output><jupyter_text>Now, all the necessary elements in order to calculate forces and moments are available<jupyter_code># Environment conditions for the current state:
environment.update(trimmed_state)

# Forces and moments calculation:
forces, moments = aircraft.calculate_forces_and_moments(trimmed_state, environment, controls0)
forces, moments<jupyter_output><empty_output><jupyter_text>The aircraft is trimmed indeed: the total forces and moments (aerodynamics + gravity + thrust) are zero  Simulation  In order to simulate the dynamics of the aircraft under certain inputs in an environment, the user can set up a simulation using a dynamic system:<jupyter_code>from pyfme.models import EulerFlatEarth, EulerFlatEarthState
from pyfme.models.dynamic_system import BodyAxisState
system = EulerFlatEarth(aircraft, environment)<jupyter_output><empty_output><jupyter_text>Constant Controls  Let's set the controls for the aircraft during the simulation. As a first step we will set them constant and equal to the trimmed values.<jupyter_code>from pyfme.utils.input_generator import Constant
controls = {
    'delta_elevator': Constant(0),
    'delta_aileron': Constant(0),
    'delta_rudder': Constant(),
    'delta_throttle': Constant(0)
}
ini_state = EulerFlatEarthState(np.array([0,0,-100,0,0,0,45,0,0,0,0,0]))
sol = system.integrate(3, ini_state, controls)
sol.y.shape<jupyter_output><empty_output><jupyter_text>Once the simulation is set, the propagation can be performed:<jupyter_code>results = sim.propagate(10)<jupyter_output>time:   0%|                                                                                     | 0/10 [00:00<?, ?it/s]




time:   1%|▌                                                                         | 0.07/10 [00:00<00:14,  1.44s/it]




time:   2%|█▎                                                                        | 0.17/10 [00:00<00:13,  1.34s/it]




time:   2%|█▎                                                         | 0.23000000000000007/10 [00:00<00:14,  1.48s/it]




time:   3%|█▊                                                          | 0.3000000000000001/10 [00:00<00:14,  1.51s/it]




time:   4%|██▎                                                         | 0.3900000000000002/10 [00:00<00:13,  1.44s/it]




time:   5%|██▋                                                        | 0.45000000000000023/10 [00:00<00:14,  1.56s/it]




time:   5%|███                                                         | 0.5100000000000002/10 [00:00<00:15,  1.68s/it]




tim[...]<jupyter_text>The results are returned in a DataFrame:<jupyter_code>results
%matplotlib inline
kwargs = {'marker': '.',
          'subplots': True,
          'sharex': True,
          'figsize': (12, 6)}
results.plot(y=['x_earth', 'y_earth', 'height'], **kwargs);
results.plot(y=['psi', 'theta', 'phi'], **kwargs);
results.plot(y=['v_north', 'v_east', 'v_down'], **kwargs);
results.plot(y=['p', 'q', 'r'], **kwargs);
results.plot(y=['alpha', 'beta', 'TAS'], **kwargs);
results.plot(y=['Fx', 'Fy', 'Fz'], **kwargs);
results.plot(y=['Mx', 'My', 'Mz'], **kwargs);
results.plot(y=['elevator', 'aileron', 'rudder', 'thrust'], **kwargs);<jupyter_output>C:\ProgramData\Anaconda3\lib\site-packages\pandas\plotting\_core.py:1716: UserWarning: Pandas doesn't allow columns to be created via a new attribute name - see https://pandas.pydata.org/pandas-docs/stable/indexing.html#attribute-access
  series.name = label<jupyter_text>Doublet  Let's set the controls for the aircraft during the simulation. As a first step we will set them constant and equal to the trimmed values.<jupyter_code>from pyfme.utils.input_generator import Doublet
de0 = trimmed_controls['delta_elevator']
controls = controls = {
    'delta_elevator': Doublet(t_init=2, T=1, A=0.1, offset=de0),
    'delta_aileron': Constant(trimmed_controls['delta_aileron']),
    'delta_rudder': Constant(trimmed_controls['delta_rudder']),
    'delta_t': Constant(trimmed_controls['delta_t'])
}
sim = Simulation(aircraft, system, environment, controls)<jupyter_output><empty_output><jupyter_text>Once the simulation is set, the propagation can be performed:<jupyter_code>results = sim.propagate(90)
results.plot(y=['x_earth', 'y_earth', 'height'], **kwargs);
results.plot(y=['psi', 'theta', 'phi'], **kwargs);
results.plot(y=['v_north', 'v_east', 'v_down'], **kwargs);
results.plot(y=['p', 'q', 'r'], **kwargs);
results.plot(y=['alpha', 'beta', 'TAS'], **kwargs);
results.plot(y=['Fx', 'Fy', 'Fz'], **kwargs);
results.plot(y=['Mx', 'My', 'Mz'], **kwargs);
results.plot(y=['elevator', 'aileron', 'rudder', 'thrust'], **kwargs);<jupyter_output><empty_output><jupyter_text>Propagating only one time step<jupyter_code>dt = 0.05  # seconds
sim = Simulation(aircraft, system, environment, controls, dt)
results = sim.propagate(0.5)
results<jupyter_output><empty_output><jupyter_text>We can propagate for one time step even once the simulation has been propagated before:<jupyter_code>results = sim.propagate(sim.time+dt)
results<jupyter_output><empty_output><jupyter_text>Notice that `results` will include the previous timesteps as well as the last one. To get just the last one one can use pandas `loc` or `iloc`:<jupyter_code>results.iloc[-1]  # last time step
results.loc[sim.time]  # results for current simulation time<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12174633222905822, 'fraction_numerical': 0.05643634642688121, 'mean_word_length': 4.195451751690228, 'pattern_counts': {'":': 0, '<': 72, '<?xml version=': 0, '>': 64, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 5, 'word_list_counts': {'cursed_substrings.json': 20, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '17', 'dir': 'jupyter-structured-clean-dedup', 'id': '216946', 'license': 'MIT', 'n_tokens_mistral': 3052, 'n_tokens_neox': 2688, 'n_words': 698, 'path': 'examples/How it works.ipynb', 'repo_name': 'jdebecdelievre/PyFME', 'size': '8452'}