Forces and Moments using UDD#
This example demonstrates the utilization of the User Defined Dynamics (UDD) feature within the Flow360 Python API to compute aerodynamic forces and moments, specifically focusing on hinge torques for control surfaces like ailerons and rudders. The script outlines the setup process, including geometry handling, mesh generation with targeted refinements, defining simulation parameters using SimulationParams
, configuring the Spalart-Allmaras turbulence model, specifying outputs, and importantly, configuring multiple UserDefinedDynamic
objects to calculate hinge torques based on runtime force and moment data from specific boundary patches.
1import flow360 as fl
2from flow360.examples import TutorialUDDForcesMoments
3
4TutorialUDDForcesMoments.get_files()
5
6project = fl.Project.from_geometry(
7 TutorialUDDForcesMoments.geometry,
8 name="Tutorial UDD forces and moments from Python",
9)
10geometry = project.geometry
11
12geometry.show_available_groupings()
13geometry.group_edges_by_tag("edgeName")
14geometry.group_faces_by_tag("groupName")
15
16with fl.SI_unit_system:
17 box1 = fl.Box(name="box1", size=[2, 6, 3], center=[6.5, 9, 0], axis_of_rotation=[0, 1, 0])
18 box2 = fl.Box(name="box2", size=[2, 6, 3], center=[6.5, -9, 0], axis_of_rotation=[0, 1, 0])
19 box3 = fl.Box(name="box3", size=[4, 8, 3], center=[12, 0, 2], axis_of_rotation=[0, 1, 0])
20 farfield = fl.AutomatedFarfield()
21 params = fl.SimulationParams(
22 meshing=fl.MeshingParams(
23 defaults=fl.MeshingDefaults(
24 surface_max_edge_length=0.5,
25 curvature_resolution_angle=10 * fl.u.deg,
26 boundary_layer_first_layer_thickness=2e-6,
27 boundary_layer_growth_rate=1.2,
28 ),
29 refinement_factor=1,
30 refinements=[
31 fl.SurfaceEdgeRefinement(
32 method=fl.AngleBasedRefinement(value=1 * fl.u.deg),
33 edges=[geometry["leadingEdge"]],
34 ),
35 fl.SurfaceEdgeRefinement(
36 method=fl.HeightBasedRefinement(value=5e-3), edges=[geometry["trailingEdge"]]
37 ),
38 fl.SurfaceRefinement(max_edge_length=0.5, faces=[geometry["wing*"]]),
39 fl.UniformRefinement(
40 name="box_refinement1", entities=[box1, box2, box3], spacing=0.2
41 ),
42 ],
43 volume_zones=[farfield],
44 ),
45 reference_geometry=fl.ReferenceGeometry(
46 area=60, moment_center=[5.7542, 0, 0], moment_length=[1, 1, 1]
47 ),
48 operating_condition=fl.AerospaceCondition(
49 velocity_magnitude=50,
50 alpha=10 * fl.u.deg,
51 atmosphere=fl.ThermalState(temperature=288.15),
52 ),
53 models=[
54 fl.Fluid(
55 navier_stokes_solver=fl.NavierStokesSolver(
56 absolute_tolerance=1e-9, linear_solver=fl.LinearSolver(max_iterations=35)
57 ),
58 turbulence_model_solver=fl.SpalartAllmaras(
59 linear_solver=fl.LinearSolver(max_iterations=25)
60 ),
61 ),
62 fl.Wall(
63 surfaces=[geometry["*Left"], geometry["*Right"], geometry["fuselage"]],
64 ),
65 fl.Freestream(surfaces=[farfield.farfield]),
66 ],
67 time_stepping=fl.Steady(max_steps=5000),
68 outputs=[
69 fl.VolumeOutput(
70 output_fields=["primitiveVars", "vorticity", "qcriterion", "Cp", "Mach"]
71 ),
72 fl.SurfaceOutput(
73 surfaces=[geometry["*"]],
74 output_fields=["primitiveVars", "Cf", "wallDistance", "Cp", "CfVec", "yPlus"],
75 ),
76 ],
77 user_defined_dynamics=[
78 fl.UserDefinedDynamic(
79 name="rightAileronHingeTorque",
80 input_vars=["forceX", "forceY", "forceZ", "momentX", "momentY", "momentZ"],
81 constants={
82 "density_kgpm3": 1.225,
83 "c_inf_mps": 340.29400580821283,
84 "l_grid_unit": 1,
85 "newCenterX": 5.7542,
86 "newCenterY": 7,
87 "newCenterZ": 0,
88 "newAxisX": 0,
89 "newAxisY": 1,
90 "newAxisZ": 0,
91 },
92 state_vars_initial_value=["0.0", "0.0", "0.0", "0.0", "0.0"],
93 update_law=[
94 "density_kgpm3 * c_inf_mps * c_inf_mps * l_grid_unit * l_grid_unit * l_grid_unit;",
95 "(momentX - ((newCenterY - momentCenterY) * forceZ - (newCenterZ - momentCenterZ) * forceY)) * state[0];",
96 "(momentY + ((newCenterX - momentCenterX) * forceZ - (newCenterZ - momentCenterZ) * forceX)) * state[0];",
97 "(momentZ - ((newCenterX - momentCenterX) * forceY - (newCenterY - momentCenterY) * forceX)) * state[0];",
98 "state[1] * newAxisX + state[2] * newAxisY + state[3] * newAxisZ;",
99 ],
100 input_boundary_patches=[geometry["aileronRight"]],
101 ),
102 fl.UserDefinedDynamic(
103 name="leftAileronHingeTorque",
104 input_vars=["forceX", "forceY", "forceZ", "momentX", "momentY", "momentZ"],
105 constants={
106 "density_kgpm3": 1.225,
107 "c_inf_mps": 340.29400580821283,
108 "l_grid_unit": 1,
109 "newCenterX": 5.7542,
110 "newCenterY": -7,
111 "newCenterZ": 0,
112 "newAxisX": 0,
113 "newAxisY": -1,
114 "newAxisZ": 0,
115 },
116 state_vars_initial_value=["0.0", "0.0", "0.0", "0.0", "0.0"],
117 update_law=[
118 "density_kgpm3 * c_inf_mps * c_inf_mps * l_grid_unit * l_grid_unit * l_grid_unit",
119 "(momentX - ((newCenterY - momentCenterY) * forceZ - (newCenterZ - momentCenterZ) * forceY)) * state[0];",
120 "(momentY + ((newCenterX - momentCenterX) * forceZ - (newCenterZ - momentCenterZ) * forceX)) * state[0];",
121 "(momentZ - ((newCenterX - momentCenterX) * forceY - (newCenterY - momentCenterY) * forceX)) * state[0];",
122 "state[1] * newAxisX + state[2] * newAxisY + state[3] * newAxisZ ",
123 ],
124 input_boundary_patches=[geometry["aileronLeft"]],
125 ),
126 fl.UserDefinedDynamic(
127 name="rightRudderHingeTorque",
128 input_vars=["forceX", "forceY", "forceZ", "momentX", "momentY", "momentZ"],
129 constants={
130 "density_kgpm3": 1.225,
131 "c_inf_mps": 340.29400580821283,
132 "l_grid_unit": 1,
133 "newCenterX": 12.01,
134 "newCenterY": 0.861,
135 "newCenterZ": 0.861,
136 "newAxisX": 0,
137 "newAxisY": 0.7071,
138 "newAxisZ": 0.7071,
139 },
140 state_vars_initial_value=["0.0", "0.0", "0.0", "0.0", "0.0"],
141 update_law=[
142 "density_kgpm3 * c_inf_mps * c_inf_mps * l_grid_unit * l_grid_unit * l_grid_unit",
143 "(momentX - ((newCenterY - momentCenterY) * forceZ - (newCenterZ - momentCenterZ) * forceY)) * state[0];",
144 "(momentY + ((newCenterX - momentCenterX) * forceZ - (newCenterZ - momentCenterZ) * forceX)) * state[0];",
145 "(momentZ - ((newCenterX - momentCenterX) * forceY - (newCenterY - momentCenterY) * forceX)) * state[0];",
146 "state[1] * newAxisX + state[2] * newAxisY + state[3] * newAxisZ ",
147 ],
148 input_boundary_patches=[geometry["rudderRight"]],
149 ),
150 fl.UserDefinedDynamic(
151 name="leftRudderHingeTorque",
152 input_vars=["forceX", "forceY", "forceZ", "momentX", "momentY", "momentZ"],
153 constants={
154 "density_kgpm3": 1.225,
155 "c_inf_mps": 340.29400580821283,
156 "l_grid_unit": 1,
157 "newCenterX": 12.01,
158 "newCenterY": -0.861,
159 "newCenterZ": 0.861,
160 "newAxisX": 0,
161 "newAxisY": -0.7071,
162 "newAxisZ": 0.7071,
163 },
164 state_vars_initial_value=["0.0", "0.0", "0.0", "0.0", "0.0"],
165 update_law=[
166 "density_kgpm3 * c_inf_mps * c_inf_mps * l_grid_unit * l_grid_unit * l_grid_unit",
167 "(momentX - ((newCenterY - momentCenterY) * forceZ - (newCenterZ - momentCenterZ) * forceY)) * state[0];",
168 "(momentY + ((newCenterX - momentCenterX) * forceZ - (newCenterZ - momentCenterZ) * forceX)) * state[0];",
169 "(momentZ - ((newCenterX - momentCenterX) * forceY - (newCenterY - momentCenterY) * forceX)) * state[0];",
170 "state[1] * newAxisX + state[2] * newAxisY + state[3] * newAxisZ ",
171 ],
172 input_boundary_patches=[geometry["rudderLeft"]],
173 ),
174 ],
175 )
176
177project.run_case(params, name="Case of tutorial UDD forces and moments from Python")
Notes#
Multiple
fl.UserDefinedDynamic
instances are configured to calculate hinge torques for different control surfaces (ailerons, rudders). Each UDD instance takes forces and moments from specifiedinput_boundary_patches
asinput_vars
.The hinge torque calculation logic resides within the
update_law
expressions, which perform moment transformations from the reference moment center to the specified hinge axis using providedconstants
(like hinge location and axis) and the input forces/moments.Mesh refinements, including
fl.UniformRefinement
usingfl.Box
volumes andfl.SurfaceEdgeRefinement
, are employed to enhance grid resolution in areas critical to accurate force prediction, such as around the wing and control surfaces.