@@ -32,11 +32,14 @@ loop of the numerical simulation.
3232
3333In this model we want to monitor the force and deflection in the damper,
3434in addition to the vertical deflection of the steering pin Triad.
35- For this purpose, three other Function objects are defined simply as 1-to-1
36- functions, where the respective response quantities are defined as arguments.
37- These three functions (marked with the "** S** " icon in the Objects three) then
38- serve as virtual sensors which can be evaluated by the calling process during
39- the time step loop.
35+ Since the damper is composed of two structural elements between the end triads,
36+ we define a * Math expression* Function object summing the Force of the Axial
37+ spring and the Axial damper. This function is then marked as Output sensor
38+ (the "** S** " icon in the Objects three). In addition, two other Function
39+ objects are defined simply as 1-to-1 functions, where the respective response
40+ quantities (the damper deformation and the Z-position of the pin Triad)
41+ are defined as arguments. These three functions will then serve as virtual
42+ sensors that can be evaluated by the calling process during the time step loop.
4043
4144## Creating the digital twin using fedempy
4245
@@ -63,13 +66,13 @@ if not path.isdir(model_file.parent):
6366 mkdir(model_file.parent)
6467
6568# Create a new FEDEM model
66- my_model = FedemModeler(str (model_file), force_new = True )
69+ my_model = FedemModeler(str (model_file), True ,
70+ name = " Short- and long-arm car front suspension" )
6771
6872# Load the FE parts
6973lca = my_model.make_fe_part(PARTS_PATH + ' lca.nas' )
7074knuckle = my_model.make_fe_part(PARTS_PATH + ' knuckle.nas' )
7175uca = my_model.make_fe_part(PARTS_PATH + ' uca.nas' )
72- ground = 2 # base id of the reference plane will always be 2
7376
7477# Lower control arm joints to ground
7578j1 = my_model.make_joint(' Fix 1' , FmType.BALL_JOINT ,
@@ -97,20 +100,23 @@ j6 = my_model.make_joint('Fix 4', FmType.BALL_JOINT,
97100t0 = my_model.make_triad(' Steering pin' , node = 2 , on_part = knuckle)
98101my_model.edit_triad(t0, constraints = {' Tx' : FmDofStat.FIXED })
99102
100- # Damper
101- t1 = my_model.make_triad(' Damper pin' , node = 11909 , on_part = lca)
102- t2 = my_model.make_triad(' Damper ground' , pos = (0.0 , 0.0 , 0.3 ), on_part = ground)
103- s1 = my_model.make_spring(' Damper' , (t1, t2), init_Stiff_Coeff = 7.5e6 )
103+ # Spring/Damper triads
104+ t1 = my_model.make_triad(' Damper connection' , node = 11909 , on_part = lca)
105+ t2 = my_model.make_triad(' Ground' , pos = (0.0 , 0.0 , 0.3 ),
106+ on_part = my_model.fm_get_refplane())
107+
108+ # Spring/Damper
109+ s1 = my_model.make_spring(' Spring' , (t1, t2), init_Stiff_Coeff = 7.5e6 )
104110d1 = my_model.make_damper(' Damper' , (t1, t2), init_Damp_Coeff = 3.0e3 )
105111
106- # Wheel hub triad with external force
112+ # Wheel hub triad with external vertical force
107113t3 = my_model.make_triad(' Wheel hub' , node = 1 , on_part = knuckle)
108- my_model.edit_triad(t3, load = {' Tz' : my_model.make_function(' Wheel force' )})
114+ my_model.edit_triad(t3, load = {' Tz' : my_model.make_function(' Wheel force' , tag = ' Wheel_Fz ' )})
109115
110116# Ouput sensors
111- o1 = my_model.make_sensor(' Damper force' , (s1, d1), FmVar.FORCE )
112- o2 = my_model.make_sensor(' Damper deflection ' , s1, FmVar.DEFLECTION )
113- o3 = my_model.make_sensor(' Steering pin deflection' , t0, FmVar.POS , FmDof.TZ )
117+ my_model.make_sensor(' Damper force' , (s1, d1), FmVar.FORCE , tag = ' damper_force ' )
118+ my_model.make_sensor(' Damper deformation ' , s1, FmVar.DEFLECTION , tag = ' damper_deformation ' )
119+ my_model.make_sensor(' Steering pin deflection' , t0, FmVar.POS , FmDof.TZ , tag = ' pin_deflection ' )
114120
115121my_model.fm_solver_setup(t_inc = 0.005 , t_end = 2.5 , t_quasi = - 1.0 )
116122my_model.fm_solver_tol(1.0e-6 ,1.0e-6 ,1.0e-6 )
@@ -123,7 +129,7 @@ Before the model can be exported, the FE models need to be reduced into
123129superelements since the FMU will only conduct the dynamics simulation
124130of the mechanism model. This can be done with ` fedempy ` using:
125131
126- $ python -m fedempy.fmm_solver -f 02-sla-dtwin.fmm --reduce-only --save-model
132+ $ python -m fedempy.fmm_solver -f 02-sla-dtwin.fmm --reduce-only
127133
128134Alternatively, you can open the generated model in the FEDEM GUI and perform the
129135model reduction there.
@@ -138,10 +144,13 @@ This dialog box shows three alternative ways of exporting the model,
138144but only the ** FMU** option is relevant here. So enable that toggle.
139145Then use the ** Browse...** button to selected the name for the fmu-file.
140146In the right side of the dialog, you find a list of the input- and output
141- indicators in the model, corresponding to the external functions and output
142- sensors defined in the modelling script.
147+ indicators in the model, corresponding to the external functions
148+ and output sensors defined in the modelling script.
149+ Notice in particular the * Name* column. The strings here will be
150+ used to identify the input and outputs in the exported FMU.
151+ They are defined using the * tag* keyword in the modelling script
143152
144- Press the ** Export** button to generate the fmu , and thenexit the FEDEM GUI.
153+ Press the ** Export** button to generate the FMU , and then exit the FEDEM GUI.
145154
146155## Testing the FMU
147156
@@ -150,9 +159,74 @@ To verify that the created FMU works, you can use a tool such as
150159you can controll the simulation. It is also convenient to make a python script
151160to run it from console or to integrate with a larger simulation environment.
152161
153- We here present a simple python driver, which just runs though the simulation
154- as it is set up in the model. The script takes the input for each time step
155- from a specified input file, and prints the output sensor values to the console.
162+ First, you can use the web-based static checker
163+ [ FMU Check] ( https://fmu-check.herokuapp.com/ ) to validate the FMU before doing
164+ any simulation attempt. For the FMU of this example
165+ (named [ sla-dtwin.fmu] ( linked_files/sla-dtwin.fmu ) ), it will produce the following:
166+
167+ ![ FMU Check] ( ../images/sla-fmu-checked.png )
168+
169+ If you have installed the FMPy module, you may also use its CLI to check it.
170+ From a console window, run the command ` $ fmpy info sla-dtwin.fmu ` :
171+
172+ $ fmpy info sla-dtwin.fmu
173+
174+ Model Info
175+
176+ FMI Version 2.0
177+ FMI Type Co-Simulation
178+ Model Name 02-sla-dtwin
179+ Description Short- and long-arm car front suspension
180+ Platforms linux64, win64
181+ Continuous States 0
182+ Event Indicators 0
183+ Variables 4
184+ Generation Tool FEDEM FMU Exporter
185+ Generation Date 2025-10-28T20:09:21
186+
187+
188+ Variables (input, output)
189+
190+ Name Causality Start Value Unit Description
191+ Wheel_Fz input 0.0 Wheel force
192+ damper_force output Damper force
193+ damper_deformation output Damper deformation
194+ pin_deflection output Steering pin deflection
195+
196+ With this looking good, the next step is to try to run the FMU.
197+ Since it has one input variable, a file containing some time history of
198+ this quantity is needed. For this test, please use the file
199+ [ sla_input.csv] ( linked_files/sla_input.csv ) . The first line of the input file
200+ needs to contain the name(s) of the input variable(s) as column headers.
201+ Otherwise it will fail, even if only one input variable is present.
202+
203+ * Note:* A FEDEM FMU only contains the simulation data model in addition to
204+ the FMU configuration files, but not the FEDEM solver itself. The FMU assumes
205+ that a working FEDEM installation exist on the host running it, and the solver
206+ is accessed via an environment variable:
207+
208+ FEDEM_SOLVER = full path to fedem_solver_core.dll or libfedem_solver_core.so
209+
210+ This environment variable needs to be defined in the shell running the FMU.
211+ You can fetch the necessary binaries from the
212+ [ fedem-solvers] ( https://github.com/openfedem/fedem-solvers/releases )
213+ repository on github. You may also use the solvers embedded with the
214+ full FEDEM GUI installation.
215+
216+ Using the FMPy GUI (` $ python -m fmpy.gui ` ), select the * sla-dtwin.fmu* file
217+ exported from the FEDEM FMU exporter, and then select * sla_input.csv* as the
218+ input file:
219+
220+ ![ FMU settings] ( ../images/sla-fmpy-setup.png )
221+
222+ Then run the simulation to produce the results:
223+
224+ ![ FMU results] ( ../images/sla-fmpy-results.png )
225+
226+ Below we present a simple python driver, which will run though the simulation
227+ as it is set up in the model in the same way as the FMPy GUI above will do.
228+ The script takes the input for each time step from a specified input file,
229+ and prints the output sensor values to the console.
156230Use this as a template for more advanced co-simulation tasks with FEDEM FMUs.
157231
158232[ Download...] ( linked_files/run_fedem_fmu.py )
@@ -190,7 +264,9 @@ def run(fmu_file, input_file=None, instance_name="my instance"):
190264 num_output = num_params[1 ] # Number of output sensors
191265
192266 # Read the input values into a Dataframe
193- if num_inputs > 0 and input_file is not None :
267+ if input_file is None :
268+ inputs = None
269+ elif num_inputs > 0 :
194270 inputs = read_csv(input_file, sep = " \t " )
195271
196272 # List of external function indices
@@ -199,18 +275,21 @@ def run(fmu_file, input_file=None, instance_name="my instance"):
199275 outIdx = range (num_inputs,num_inputs+ num_output)
200276
201277 # Time loop, this will run through the FEDEM simulation
202- # using the time domain setup in the model file used to export the FMU.
278+ # using the time domain setup of the model file used to export the FMU.
203279 # The two parameters to the doStep() call are dummies (time and step size).
204280 # They are not used in the FMU so the values are arbitrary (2*0.0 is fine).
205- istep = 0
281+ step = 0
206282 while not fmu.getBooleanStatus(fmi2.fmi2Terminated):
207- if num_inputs > 0 : # Set the external function values for this step
208- fmu.setReal([* inpIdx], inputs.iloc[istep])
209- fmu.doStep(0.0 , 0.0 )
283+ if inputs is not None : # Set external function values for next step
284+ # First column of inputs is not used, assuming it contains the time
285+ fmu.setReal([* inpIdx], inputs.iloc[step].tolist()[1 :])
286+
287+ fmu.doStep(0.0 , 0.0 ) # Advance the simulation on step
288+
289+ step += 1
210290 time = fmu.getRealStatus(fmi2.fmi2LastSuccessfulTime)
211291 output = fmu.getReal([* outIdx])
212- istep += 1
213- print (f " Here are the outputs at step= { istep} time= { time} : " , output)
292+ print (f " Here are the outputs at step= { step} time= { time} : " , output)
214293
215294 # Finished, close down
216295 fmu.terminate()
0 commit comments