Replies: 17 comments 19 replies
-
|
Some initial tidbit responses.
What about mass? Fuel burn in the propulsion model calculates a delta mass and integrates it during each time step internally. Should position not be part of the state?
In terms of your current state vector jsbsim/src/models/FGPropagate.cpp Lines 227 to 232 in 3003423
Lines 216 to 241 in 3003423 |
Beta Was this translation helpful? Give feedback.
-
|
Before going too much into the (re)design, I think it's worth to put here the motivation behind the current state of the code, more specifically about the existence of Below is the data flow that was existing in JSBSim before @jonsberndt had introduced the mediator pattern, I think this plot speaks for itself:
Unfortunately, the discussion did not take place in the developer mailing list so I have put one excerpt below. The text is from Jon (discussion dating from Jul 2nd 2011 😉): The rest of the discussion focused on the merits of this approach, basically the raised concerns were that this change could obfuscate (sic) the code while the benefits were not so obvious. Eventually, the consensus was reached and the change applied. |
Beta Was this translation helpful? Give feedback.
-
|
I don't want to paint a grim picture but another point that is making things a bit more complex is that some classes are maintaining some internal state that is modified each time they are executed. jsbsim/src/models/flight_control/FGFilter.cpp Lines 112 to 150 in 3003423
|
Beta Was this translation helpful? Give feedback.
-
I understand the inherent difficulties involved in choosing the “Mediator Pattern.” |
Beta Was this translation helpful? Give feedback.
-
Sorry, I was in a rush and had misread
You've updated the state to include mass and the moments of inertia, however remember that
The vast majority of the properties available via the property manager fall into your definition of |
Beta Was this translation helpful? Give feedback.
-
|
This is a very interesting discussion, and it is clear that @agodemar has put a lot of thought into this. Conceptually, I am not against the idea of improvements to the state propagation code. It probably could be made more clear and more functional. At the same time - as @bcoconni stated, the design we have now did not happen by chance, and it evolved over many years. Any changes to the state propagation code would be like "open-heart surgery," since this code is arguably the heart of the flight dynamics model code. I might say that the code is like a Jenga tower, currently structurally stable, but potentially fragile. I remember once that we considered adding an implicit integration capability to JSBSim for state propagation - like an RK4 integrator. But, due to the nature of the JSBSim architecture, that didn't work out, and I think that in the attempt to add it one of the big stumbling blocks we found was the way that the property system worked - or didn't - with mid-timestep needs to make the RK integration scheme work. But, that was a long time ago, so I may be remembering that wrong. In any case, since in git we can have our own development branches, it wouldn't hurt to experiment with that concept. If it did turn out to be something that looked increasingly desirable, maybe it would even motivate me to implement the 6-DoF simulation check cases that I helped to develop about 11 years ago in cooperation with some NASA engineers. Those could be helpful in validating any changes to the state propagation code. I need to review Agostino's proposal that started this thread again when I get some free time. |
Beta Was this translation helpful? Give feedback.
-
|
So I've been playing a bit with the PathView/PathSim generated Python code that @agodemar provided recently, see: https://github.com/agodemar/jsbsim/blob/master/examples/python/test_02_pathview_graph.py In summary it's making use of a PathSim So something along these lines when integrated into a PathView/PathSim environment, say with some basic feedback control.
So, in the current setup the JSBSim state integration happens internally. Note JSBSim uses different integrators for the angular state versus the translational state. Not having a formal education in control systems etc. are there major limitations with this approach? Are there for example control systems we couldn't implement? In the meantime, mainly to get more familiar with PathView/PathSim I've started looking at implementing a custom JSBSim specific PathSim block, using the For example, provide trim parameters (airspeed, altitude, gamma) that can be set via the PathView GUI block editor and have the code use them at the appropriate time to perform a trim, i.e. the user doesn't need to type in the trim code. Allow the user to specify a set of JSBSim properties for the input vector |
Beta Was this translation helpful? Give feedback.
-
|
Hi @milanofthe thanks for jumping in. While I was debugging the example to figure out why the aircraft wasn't staying in trim until the doublet was introduced I took a brief look at the documentation of the following blocks to try and see if one of them was a better fit for hosting JSBSim. I did see the comment in the documentation for Function - "will be called multiple times per timestep", which is partly why I looked at some of the other block options, but when I put a debug print statement in the function was being called exactly 40s * 120Hz times. I'll switch to using Wrapper and test that out. One quick question, is there a way for our function passed to the Wrapper block to know when the simulation starts, an event, or checking for time == 0? The reason I ask is that I'd like to execute a trim operation at this time. |
Beta Was this translation helpful? Give feedback.
-
|
Didn't have any issues switching from Overrode the class JSBSimWrapper(Wrapper):
def __init__(self, func, T=1/120, **kwargs):
super().__init__(func=func, T=T, **kwargs)
self.fdm = None
def update(self, t):
if t == 0 and self.fdm is None:
# Init JSBSim, load aircraft, initial conditions and trim
return super().update(t) |
Beta Was this translation helpful? Give feedback.
-
|
Just wanted to double-check with regards to PathView, since I've been doing all my testing with a stand-alone python file and PathSim. In PathView will it create a new instance of the JSBSimWrapper block each time the user clicks on the Play button? |
Beta Was this translation helpful? Give feedback.
-
|
@milanofthe can you confirm if this is the best approach to generically handle a variable number of entries in the input vector I was thinking of providing a 'Input Properties' and 'Output Properties' set of parameters to the JSBSimWrapper, which the user would then populate via the PathView properties GUI. Example snipped from the Scope block.
Then with the following code in JSBSimWrapper. class JSBSimWrapper(Wrapper):
def __init__(self, T=1/120, input_properties=None, output_properties=None):
super().__init__(func=self._func, T=T)
self.input_properties = input_properties if input_properties is not None else []
self.output_properties = output_properties if output_properties is not None else []
# Init JSBSim, load aircraft, initial conditions and trim
self.init_fdm()
self.trim()
def _func(self, *u): # Need to accept *u to be compatible with PathSim's Wrapper
# Confirm that the input vector u has the expected length
if len(u) != len(self.input_properties):
raise ValueError(f"Expected {len(self.input_properties)} inputs, but got {len(u)}")
# Pass input vector u to JSBSim by setting the corresponding properties
for i in range(len(u)):
self.fdm[self.input_properties[i]] = u[i]
self.fdm.run() # Run the JSBSim model for one time step
# Extract output properties from JSBSim and return as vector y
y = []
for i in range(len(self.output_properties)):
y.append(self.fdm[self.output_properties[i]])
return y |
Beta Was this translation helpful? Give feedback.
-
|
@milanofthe and something along these lines? None for input and output in order to support variable/unlimited ports. @classmethod
def info(cls):
return {
"input_port_labels": None,
"output_port_labels": None,
"parameters": {
"aircraft model": {"default": "737"},
"trim airspeed": {"default": 200},
"trim altitude": {"default": 1000},
"trim gamma": {"default": 0}
},
"description": "A JSBSim block."
} |
Beta Was this translation helpful? Give feedback.
-
|
@milanofthe I assume PathView makes use of the Given we're inheriting from I assume PathView also makes use of the In terms of my @classmethod
def info(cls):
return {
"input_port_labels": None,
"output_port_labels": None,
"parameters": {
"aircraft model": {"default": "737"},
"trim airspeed": {"default": 200},
"trim altitude": {"default": 1000},
"trim gamma": {"default": 0},
"input_properties": {"default": None},
"output_properties": {"default": None},
},
"description": "A JSBSim block."
}So I also need to update the class JSBSimWrapper(Wrapper):
def __init__(self, T=1/120, input_properties=None, output_properties=None):
super().__init__(func=self._func, T=T)to: class JSBSimWrapper(Wrapper):
def __init__(self, T=1/120, input_properties=None, output_properties=None,
aircraft_model='737', 'trim_airspeed'=200, trim_altitude=1000, trim_gamma=0):
super().__init__(func=self._func, T=T)Once I've done a bit more testing with a stand-alone PathSim python file, then I'll start looking at trying to add this custom JSBSimWrapper block to PathView. |
Beta Was this translation helpful? Give feedback.
-
|
So here is a first complete draft of the JSBSimWrapper for PathSim.
from pathsim.blocks import Wrapper
import jsbsim
#
class JSBSimWrapper(Wrapper):
# TODO: Probably need to add some additional parameters, e.g. ground trim versus full air trim versus no trim,
# gear position, flap position.
@classmethod
def info(cls):
return {
"input_port_labels": None,
"output_port_labels": None,
"parameters": {
"JSBSim path": {"default": None},
"T": {"default": 1/120},
"aircraft model": {"default": "737"},
"trim airspeed": {"default": 200},
"trim altitude": {"default": 1000},
"trim gamma": {"default": 0},
"input_properties": {"default": None},
"output_properties": {"default": None},
},
"description": "A JSBSim block."
}
def __init__(self, T=1/120, input_properties=None, output_properties=None, JSBSim_path=None,
aircraft_model='737', trim_airspeed=200, trim_altitude=1000, trim_gamma=0):
super().__init__(func=self._func, T=T)
self.input_properties = input_properties if input_properties is not None else []
self.output_properties = output_properties if output_properties is not None else []
# Init JSBSim, load aircraft, trim with initial conditions
self.init_fdm(JSBSim_path, aircraft_model, T)
self.trim(trim_airspeed, trim_altitude, trim_gamma)
def init_fdm(self, JSBSim_path, aircraft_model, T):
# Avoid flooding the console with log messages
jsbsim.FGJSBBase().debug_lvl = 0
# Create a flight dynamics model (FDM) instance.
# None for JSBSim_path means it will look for JSBSim files in pip install location.
self.fdm = jsbsim.FGFDMExec(JSBSim_path)
# Load the aircraft model
self.fdm.load_model(aircraft_model)
# Set the time step
self.fdm.set_dt(T)
def trim(self, trim_airspeed, trim_alitude, trim_gamma):
# Set engines running
self.fdm['propulsion/set-running'] = -1
# Set initial conditions for trim
self.fdm['ic/h-sl-ft'] = trim_alitude
self.fdm['ic/vc-kts'] = trim_airspeed
self.fdm['ic/gamma-deg'] = trim_gamma
self.fdm.run_ic()
# Calculate trim solution
self.fdm['simulation/do_simple_trim'] = 1
def _func(self, *u):
# PathSim's Wrapper base class passes the input vector as separate arguments, so we need to collect them into a list
# Confirm that the input vector u has the expected length
if len(u) != len(self.input_properties):
raise ValueError(f"Expected {len(self.input_properties)} inputs, but got {len(u)}")
# Pass input vector u to JSBSim by setting the corresponding properties
for i in range(len(u)):
self.fdm[self.input_properties[i]] = u[i]
# Run the JSBSim model for one time step
self.fdm.run()
# Extract output properties from JSBSim and return as vector y
y = []
for i in range(len(self.output_properties)):
y.append(self.fdm[self.output_properties[i]])
return yI then used PathView to create this set of interconnected blocks, using the generic Wrapper block. Roughly matching @agodemar's original example in terms of applying an elevator doublet and then plotting the elevator input and the resulting AoA. I also added the pitch angle as an output.
I then grabbed the generated python code for this and hand edited it to replace the generic Wrapper block with the JSBSimWrapper block, and to set the JSBSim input and output properties.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
PathSim Simulation
==================
Generated by PathView on 2026-03-02 18:33:44
https://view.pathsim.org
PathSim documentation: https://docs.pathsim.org
"""
# ────────────────────────────────────────────────────────────────────────────
# IMPORTS
# ────────────────────────────────────────────────────────────────────────────
import numpy as np
import matplotlib.pyplot as plt
from pathsim import Simulation, Connection
from pathsim.blocks import (
Scope,
StepSource
)
from pathsim.solvers import SSPRK22
# ────────────────────────────────────────────────────────────────────────────
# USER-DEFINED CODE
# ────────────────────────────────────────────────────────────────────────────
from JSBSimWrapper import JSBSimWrapper
# ────────────────────────────────────────────────────────────────────────────
# BLOCKS
# ────────────────────────────────────────────────────────────────────────────
# Sources
stepsource = StepSource(
amplitude=[-0.1, 0, 0.1, 0],
tau=[10, 11, 12, 13]
)
# Mixed
jsbsim = JSBSimWrapper(
input_properties=['fcs/elevator-cmd-norm'],
output_properties=['aero/alpha-deg', 'attitude/theta-deg'],
)
# Recording
control_scope = Scope(
labels=['Elevator']
)
output_scope = Scope(
labels=['AoA', 'Pitch']
)
blocks = [
stepsource,
jsbsim,
control_scope,
output_scope,
]
# ────────────────────────────────────────────────────────────────────────────
# CONNECTIONS
# ────────────────────────────────────────────────────────────────────────────
conn_0 = Connection(jsbsim[0], output_scope[0])
conn_1 = Connection(jsbsim[1], output_scope[1])
conn_2 = Connection(stepsource[0], jsbsim[0])
conn_3 = Connection(stepsource[0], control_scope[0])
connections = [
conn_0,
conn_1,
conn_2,
conn_3,
]
# ────────────────────────────────────────────────────────────────────────────
# SIMULATION
# ────────────────────────────────────────────────────────────────────────────
sim = Simulation(
blocks,
connections,
Solver=SSPRK22,
dt=1/120,
dt_min=1e-16,
tolerance_lte_rel=0.0001,
tolerance_lte_abs=1e-08,
tolerance_fpi=1e-10,
)
# ────────────────────────────────────────────────────────────────────────────
# MAIN
# ────────────────────────────────────────────────────────────────────────────
if __name__ == '__main__':
# Run simulation
sim.run(duration=40.0)
# Plot results
sim.plot()
plt.show()When run it generates the following plots.
Some outstanding things:
@milanofthe a couple of questions.
|
Beta Was this translation helpful? Give feedback.
-
|
@seanmcleod70 the pathsim-flight repo is public now: https://github.com/pathsim/pathsim-flight |
Beta Was this translation helpful? Give feedback.
-
|
@milanofthe in terms of the Originally, I came across this documentation: https://github.com/pathsim/pathview/blob/main/docs/toolbox-spec.md#21-block-classes
It does also mention:
So, I took this to mean that I should be implementing Now I did wonder though, when looking at the So taking a look at # Get __init__ signature for parameters
sig = inspect.signature(cls.__init__)
params = {
name: {
"default": None if param.default is inspect.Parameter.empty else param.default
}
for name, param in sig.parameters.items()
if name not in ("self", "kwargs", "args")
}
return {
"type": cls.__name__,
"description": cls.__doc__,
"input_port_labels": cls.input_port_labels,
"output_port_labels": cls.output_port_labels,
"parameters": params,
}I see how it uses introspection of the class to build the So should I ignore the documentation:
|
Beta Was this translation helpful? Give feedback.
-
|
@milanofthe I've modified all my code to remove the implementation of I've just submitted a PR to https://github.com/pathsim/pathsim-flight with the updated code. |
Beta Was this translation helpful? Give feedback.







Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I would like to open a technical discussion to prepare a structured revision process for JSBSim’s flight simulation state propagation procedure, centered on:
FGPropagateFGFDMExecThis discussion is grounded in:
The objective is to clearly define the state propagation contract and assess whether the current implementation and execution ordering reflect it cleanly and consistently.
1. Formal Discrete-Time Propagation Model
We express the simulation as a discrete-time integration problem, where time is sampled at$t_i$ and $i$ is an integer index.
1.1 State Vector Definition
We define the propagated state vector as:
where:
1.2 Derivative (Dynamics) Model
At each discrete time$t_i$ , the derivative is defined as:
where:
Including$\dot{\boldsymbol{x}}(t_{i-1})$ makes explicit what is already implicit in many simulator implementations:
This formulation clarifies the conceptual boundary between:
1.3 Numerical Integration Step
The state update is:
The integral is approximated using the integrator selected inside
FGPropagate.This makes explicit that:
FGPropagateperforms numerical integrationFGFDMExecdetermines execution ordering and scheduling2. Observation Equation (Published Outputs)
In addition to propagation, the simulator produces outputs:
where:
This observation equation formalizes what is currently scattered across model execution and property publication.
It also connects directly to the API concern raised in #1390:
get_uvw(),get_Tec2b())get_state()orget_derivatives()interfaceIf we define$\boldsymbol{x}$ , $\dot{\boldsymbol{x}}$ , and $\boldsymbol{y}$ clearly, we can define a stable introspection API accordingly.
3. Discussion Topics
3.1 State Definition and Ownership
3.2 Derivative Boundary
3.3 Scheduling and Causality (
FGFDMExec)Does
FGFDMExec::eModelsdefine execution order?Does the current scheduling reflect the clean sequence:
Are there implicit dependencies that violate this conceptual ordering?
3.4 Integrator and Freeze Behavior
4. Suggested Outcome
If consensus emerges that clarification or restructuring is beneficial:
Contributions are especially welcome from those who have worked on:
FGPropagateFGFDMExecschedulingThe goal is not to rewrite propagation blindly, but to formalize the contract first, then align the implementation with it in a controlled, test-backed manner.
Beta Was this translation helpful? Give feedback.
All reactions