Automotive Report#
This script demonstrates the automated generation of a comprehensive aerodynamic report for the DrivAer automotive model using the Flow360 Python API and its reporting plugin.
It sets up and runs a series of steady-state simulations at varying yaw angles (beta = 0/5/10 degrees).
Post-simulation, it utilizes the flow360.plugins.report
module to compile results, including statistical data, force coefficients, and various flow visualizations (e.g., surface pressure coefficient, wall shear stress, flow field slices, isosurfaces) into a structured PDF document.
1import flow360 as fl
2from flow360 import u
3from flow360.examples import DrivAer
4from flow360.log import log
5from flow360.plugins.report.report import ReportTemplate
6from flow360.plugins.report.report_items import (
7 BottomCamera,
8 Chart2D,
9 Chart3D,
10 FrontCamera,
11 FrontLeftBottomCamera,
12 FrontLeftTopCamera,
13 Inputs,
14 LeftCamera,
15 RearCamera,
16 RearLeftTopCamera,
17 RearRightBottomCamera,
18 Settings,
19 Summary,
20 Table,
21 TopCamera,
22)
23from flow360.plugins.report.utils import Average, DataItem, Delta, Expression, Variable
24from flow360.version import __solver_version__
25
26DrivAer.get_files()
27
28project = fl.Project.from_volume_mesh(
29 DrivAer.mesh_filename,
30 name="Automotive DrivAer",
31)
32
33vm = project.volume_mesh
34
35log.info("Volume mesh contains the following boundaries:")
36for boundary in vm.boundary_names:
37 log.info("Boundary: " + boundary)
38
39freestream_surfaces = ["blk-1/WT_side1", "blk-1/WT_side2", "blk-1/WT_inlet", "blk-1/WT_outlet"]
40slip_wall_surfaces = ["blk-1/WT_ceiling", "blk-1/WT_ground_front", "blk-1/WT_ground"]
41wall_surfaces = list(set(vm.boundary_names) - set(freestream_surfaces) - set(slip_wall_surfaces))
42
43cases = []
44
45for beta in [0, 5, 10]:
46 with fl.SI_unit_system:
47 params = fl.SimulationParams(
48 meshing=None,
49 reference_geometry=fl.ReferenceGeometry(area=2.17, moment_length=2.7862),
50 operating_condition=fl.AerospaceCondition(velocity_magnitude=40, beta=beta * u.deg),
51 models=[
52 fl.Wall(surfaces=[vm[i] for i in wall_surfaces], use_wall_function=True),
53 fl.Freestream(
54 surfaces=[vm[i] for i in freestream_surfaces],
55 ),
56 fl.SlipWall(
57 surfaces=[vm[i] for i in slip_wall_surfaces],
58 ),
59 ],
60 user_defined_fields=[
61 fl.UserDefinedField(
62 name="Cpx",
63 expression="double prel = primitiveVars[4] - pressureFreestream;"
64 + "double PressureForce_X = prel * nodeNormals[0]; "
65 + "Cpx = PressureForce_X / (0.5 * MachRef * MachRef) / magnitude(nodeNormals);",
66 ),
67 ],
68 outputs=[
69 fl.SurfaceOutput(
70 surfaces=vm["*"],
71 output_fields=[
72 "Cp",
73 "Cf",
74 "yPlus",
75 "CfVec",
76 "primitiveVars",
77 "wall_shear_stress_magnitude",
78 "Cpx",
79 ],
80 ),
81 fl.SliceOutput(
82 entities=[
83 *[
84 fl.Slice(
85 name=f"slice_y_{name}",
86 normal=(0, 1, 0),
87 origin=(0, y, 0),
88 )
89 for name, y in zip(
90 ["0", "0_2", "0_4", "0_6", "0_8"], [0, 0.2, 0.4, 0.6, 0.8]
91 )
92 ],
93 *[
94 fl.Slice(
95 name=f"slice_z_{name}",
96 normal=(0, 0, 1),
97 origin=(0, 0, z),
98 )
99 for name, z in zip(
100 ["neg0_2", "0", "0_2", "0_4", "0_6", "0_8"],
101 [-0.2, 0, 0.2, 0.4, 0.6, 0.8],
102 )
103 ],
104 ],
105 output_fields=["velocity", "velocity_x", "velocity_y", "velocity_z"],
106 ),
107 fl.IsosurfaceOutput(
108 output_fields=["Cp", "Mach"],
109 isosurfaces=[
110 fl.Isosurface(
111 name="isosurface-cpt",
112 iso_value=-1,
113 field="Cpt",
114 ),
115 ],
116 ),
117 fl.ProbeOutput(
118 entities=[fl.Point(name="point1", location=(10, 0, 1))],
119 output_fields=["velocity"],
120 ),
121 ],
122 )
123
124 case_new = project.run_case(params=params, name=f"DrivAer 5.7M - beta={beta}")
125
126 cases.append(case_new)
127
128# wait until all cases finish running
129for case in cases:
130 case.wait()
131
132exclude = ["blk-1/WT_ground_close", "blk-1/WT_ground_patch"]
133size = "5.7M"
134
135exclude += freestream_surfaces + slip_wall_surfaces
136
137top_camera = TopCamera(pan_target=(1.5, 0, 0), dimension=5, dimension_dir="width")
138top_camera_slice = TopCamera(pan_target=(2.5, 0, 0), dimension=8, dimension_dir="width")
139side_camera = LeftCamera(pan_target=(1.5, 0, 0), dimension=5, dimension_dir="width")
140side_camera_slice = LeftCamera(pan_target=(2.5, 0, 1.5), dimension=8, dimension_dir="width")
141rear_camera = RearCamera(dimension=2.5, dimension_dir="width")
142front_camera = FrontCamera(dimension=2.5, dimension_dir="width")
143bottom_camera = BottomCamera(pan_target=(1.5, 0, 0), dimension=5, dimension_dir="width")
144front_left_bottom_camera = FrontLeftBottomCamera(
145 pan_target=(1.5, 0, 0), dimension=5, dimension_dir="width"
146)
147rear_right_bottom_camera = RearRightBottomCamera(
148 pan_target=(1.5, 0, 0), dimension=6, dimension_dir="width"
149)
150front_left_top_camera = FrontLeftTopCamera(
151 pan_target=(1.5, 0, 0), dimension=6, dimension_dir="width"
152)
153rear_left_top_camera = RearLeftTopCamera(pan_target=(1.5, 0, 0), dimension=6, dimension_dir="width")
154
155cameras_geo = [
156 top_camera,
157 side_camera,
158 rear_camera,
159 bottom_camera,
160 front_left_bottom_camera,
161 rear_right_bottom_camera,
162]
163
164limits_cp = [(-1, 1), (-1, 1), (-1, 1), (-0.3, 0), (-0.3, 0), (-1, 1), (-1, 1), (-1, 1)]
165cameras_cp = [
166 front_camera,
167 front_left_top_camera,
168 side_camera,
169 rear_left_top_camera,
170 rear_camera,
171 bottom_camera,
172 front_left_bottom_camera,
173 rear_right_bottom_camera,
174]
175
176avg = Average(fraction=0.1)
177CD = DataItem(data="surface_forces/totalCD", exclude=exclude, title="CD", operations=avg)
178
179CL = DataItem(data="surface_forces/totalCL", exclude=exclude, title="CL", operations=avg)
180
181CDA = DataItem(
182 data="surface_forces",
183 exclude=exclude,
184 title="CD*area",
185 variables=[Variable(name="area", data="params.reference_geometry.area")],
186 operations=[Expression(expr="totalCD * area"), avg],
187)
188
189CLf = DataItem(
190 data="surface_forces",
191 exclude=exclude,
192 title="CLf",
193 operations=[Expression(expr="1/2*totalCL + totalCMy"), avg],
194)
195
196CLr = DataItem(
197 data="surface_forces",
198 exclude=exclude,
199 title="CLr",
200 operations=[Expression(expr="1/2*totalCL - totalCMy"), avg],
201)
202
203CFy = DataItem(data="surface_forces/totalCFy", exclude=exclude, title="CS", operations=avg)
204
205statistical_data = [
206 "params/reference_geometry/area",
207 CD,
208 CDA,
209 Delta(data=CD),
210 CL,
211 CLf,
212 CLr,
213 CFy,
214 "volume_mesh/stats/n_nodes",
215 "params/time_stepping/max_steps",
216]
217statistical_table = Table(
218 data=statistical_data,
219 section_title="Statistical data",
220 formatter=[
221 (
222 None
223 if d
224 in [
225 "params/reference_geometry/area",
226 "volume_mesh/stats/n_nodes",
227 "params/time_stepping/max_steps",
228 ]
229 else ".4f"
230 )
231 for d in statistical_data
232 ],
233)
234
235geometry_screenshots = [
236 Chart3D(
237 section_title="Geometry",
238 items_in_row=2,
239 force_new_page=True,
240 show="boundaries",
241 camera=camera,
242 exclude=exclude,
243 fig_name=f"geo_{i}",
244 )
245 for i, camera in enumerate(cameras_geo)
246]
247cpt_screenshots = [
248 Chart3D(
249 section_title="Isosurface, Cpt=-1",
250 items_in_row=2,
251 force_new_page=True,
252 show="isosurface",
253 iso_field="Cpt",
254 exclude=exclude,
255 camera=camera,
256 )
257 for camera in cameras_cp
258]
259cfvec_screenshots = [
260 Chart3D(
261 section_title="CfVec",
262 items_in_row=2,
263 force_new_page=True,
264 show="boundaries",
265 field="CfVec",
266 mode="lic",
267 limits=(1e-4, 10),
268 is_log_scale=True,
269 exclude=exclude,
270 camera=camera,
271 )
272 for camera in cameras_cp
273]
274y_slices_lic_screenshots = [
275 Chart3D(
276 section_title=f"Slice velocity LIC y={y}",
277 items_in_row=2,
278 force_new_page=True,
279 show="slices",
280 include=[f"slice_y_{name}"],
281 field="velocityVec",
282 mode="lic",
283 limits=(0 * u.m / u.s, 50 * u.m / u.s),
284 camera=side_camera_slice,
285 fig_name=f"slice_y_vec_{name}",
286 )
287 for name, y in zip(["0", "0_2", "0_4", "0_6", "0_8"], [0, 0.2, 0.4, 0.6, 0.8])
288]
289y_slices_screenshots = [
290 Chart3D(
291 section_title=f"Slice velocity y={y}",
292 items_in_row=2,
293 force_new_page=True,
294 show="slices",
295 include=[f"slice_y_{name}"],
296 field="velocity",
297 limits=(0 * u.m / u.s, 50 * u.m / u.s),
298 camera=side_camera_slice,
299 fig_name=f"slice_y_{name}",
300 )
301 for name, y in zip(["0", "0_2", "0_4", "0_6", "0_8"], [0, 0.2, 0.4, 0.6, 0.8])
302]
303y_slices_lic_screenshots = [
304 Chart3D(
305 section_title=f"Slice velocity LIC y={y}",
306 items_in_row=2,
307 force_new_page=True,
308 show="slices",
309 include=[f"slice_y_{name}"],
310 field="velocityVec",
311 mode="lic",
312 limits=(0 * u.m / u.s, 50 * u.m / u.s),
313 camera=side_camera_slice,
314 fig_name=f"slice_y_vec_{name}",
315 )
316 for name, y in zip(["0", "0_2", "0_4", "0_6", "0_8"], [0, 0.2, 0.4, 0.6, 0.8])
317]
318z_slices_screenshots = [
319 Chart3D(
320 section_title=f"Slice velocity z={z}",
321 items_in_row=2,
322 force_new_page=True,
323 show="slices",
324 include=[f"slice_z_{name}"],
325 field="velocity",
326 limits=(0 * u.m / u.s, 50 * u.m / u.s),
327 camera=top_camera_slice,
328 fig_name=f"slice_z_{name}",
329 )
330 for name, z in zip(["neg0_2", "0", "0_2", "0_4", "0_6", "0_8"], [-0.2, 0, 0.2, 0.4, 0.6, 0.8])
331]
332y_plus_screenshots = [
333 Chart3D(
334 section_title="y+",
335 items_in_row=2,
336 show="boundaries",
337 field="yPlus",
338 exclude=exclude,
339 limits=(0, 5),
340 camera=camera,
341 fig_name=f"yplus_{i}",
342 )
343 for i, camera in enumerate([top_camera, bottom_camera])
344]
345cp_screenshots = [
346 Chart3D(
347 section_title="Cp",
348 items_in_row=2,
349 show="boundaries",
350 field="Cp",
351 exclude=exclude,
352 limits=limits,
353 camera=camera,
354 fig_name=f"cp_{i}",
355 )
356 for i, (limits, camera) in enumerate(zip(limits_cp, cameras_cp))
357]
358cpx_screenshots = [
359 Chart3D(
360 section_title="Cpx",
361 items_in_row=2,
362 show="boundaries",
363 field="Cpx",
364 exclude=exclude,
365 limits=(-0.3, 0.3),
366 camera=camera,
367 fig_name=f"cpx_{i}",
368 )
369 for i, camera in enumerate(cameras_cp)
370]
371wall_shear_screenshots = [
372 Chart3D(
373 section_title="Wall shear stress magnitude",
374 items_in_row=2,
375 show="boundaries",
376 field="wallShearMag",
377 exclude=exclude,
378 limits=(0 * u.Pa, 5 * u.Pa),
379 camera=camera,
380 fig_name=f"wallShearMag_{i}",
381 )
382 for i, camera in enumerate(cameras_cp)
383]
384
385report = ReportTemplate(
386 title="Aerodynamic analysis of DrivAer",
387 items=[
388 Summary(),
389 Inputs(),
390 statistical_table,
391 Chart2D(
392 x="x_slicing_force_distribution/X",
393 y="x_slicing_force_distribution/totalCumulative_CD_Curve",
394 fig_name="totalCumulative_CD_Curve",
395 background="geometry",
396 exclude=exclude,
397 ),
398 Chart2D(
399 x="surface_forces/pseudo_step",
400 y="surface_forces/totalCD",
401 section_title="Drag Coefficient",
402 fig_name="cd_fig",
403 exclude=exclude,
404 focus_x=(1 / 3, 1),
405 ),
406 *geometry_screenshots,
407 *cp_screenshots,
408 *cpx_screenshots,
409 *cpt_screenshots,
410 *y_slices_screenshots,
411 *y_slices_lic_screenshots,
412 *z_slices_screenshots,
413 *y_plus_screenshots,
414 *wall_shear_screenshots,
415 ],
416 settings=Settings(dpi=150),
417)
418
419report = report.create_in_cloud(
420 f"{size}-{len(cases)}cases-slices-using-groups-Cpt, Cpx, wallShear, dpi=default",
421 cases,
422 solver_version=__solver_version__,
423)
424
425report.wait()
426report.download("report.pdf")
Notes#
Demonstrates the use of the report module, including
ReportTemplate
and various report items (e.g.,Chart3D
,Table
,DataItem
), to automatically generate a detailed PDF report from simulation data.Illustrates a workflow for conducting a parametric study by programmatically submitting multiple simulation cases with varying operating conditions (yaw angle
beta
) using theProject
interface.Shows the configuration of diverse output types (
SurfaceOutput
,SliceOutput
,IsosurfaceOutput
) to capture data for detailed flow analysis and visualization within the report.