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 outputs=[
61 fl.SurfaceOutput(
62 surfaces=vm["*"],
63 output_fields=[
64 "Cp",
65 "Cf",
66 "yPlus",
67 "CfVec",
68 "primitiveVars",
69 "wall_shear_stress_magnitude",
70 ],
71 ),
72 fl.SliceOutput(
73 entities=[
74 *[
75 fl.Slice(
76 name=f"slice_y_{name}",
77 normal=(0, 1, 0),
78 origin=(0, y, 0),
79 )
80 for name, y in zip(
81 ["0", "0_2", "0_4", "0_6", "0_8"], [0, 0.2, 0.4, 0.6, 0.8]
82 )
83 ],
84 *[
85 fl.Slice(
86 name=f"slice_z_{name}",
87 normal=(0, 0, 1),
88 origin=(0, 0, z),
89 )
90 for name, z in zip(
91 ["neg0_2", "0", "0_2", "0_4", "0_6", "0_8"],
92 [-0.2, 0, 0.2, 0.4, 0.6, 0.8],
93 )
94 ],
95 ],
96 output_fields=["velocity", "velocity_x", "velocity_y", "velocity_z"],
97 ),
98 fl.IsosurfaceOutput(
99 output_fields=["Cp", "Mach"],
100 isosurfaces=[
101 fl.Isosurface(
102 name="isosurface-cpt",
103 iso_value=-1,
104 field="Cpt",
105 ),
106 ],
107 ),
108 fl.ProbeOutput(
109 entities=[fl.Point(name="point1", location=(10, 0, 1))],
110 output_fields=["velocity"],
111 ),
112 ],
113 )
114
115 case_new = project.run_case(params=params, name=f"DrivAer 5.7M - beta={beta}")
116
117 cases.append(case_new)
118
119# wait until all cases finish running
120for case in cases:
121 case.wait()
122
123exclude = ["blk-1/WT_ground_close", "blk-1/WT_ground_patch"]
124size = "5.7M"
125
126exclude += freestream_surfaces + slip_wall_surfaces
127
128SOLVER_VERSION = "report-25.4.0"
129
130top_camera = TopCamera(pan_target=(1.5, 0, 0), dimension=5, dimension_dir="width")
131top_camera_slice = TopCamera(pan_target=(2.5, 0, 0), dimension=8, dimension_dir="width")
132side_camera = LeftCamera(pan_target=(1.5, 0, 0), dimension=5, dimension_dir="width")
133side_camera_slice = LeftCamera(pan_target=(2.5, 0, 1.5), dimension=8, dimension_dir="width")
134rear_camera = RearCamera(dimension=2.5, dimension_dir="width")
135front_camera = FrontCamera(dimension=2.5, dimension_dir="width")
136bottom_camera = BottomCamera(pan_target=(1.5, 0, 0), dimension=5, dimension_dir="width")
137front_left_bottom_camera = FrontLeftBottomCamera(
138 pan_target=(1.5, 0, 0), dimension=5, dimension_dir="width"
139)
140rear_right_bottom_camera = RearRightBottomCamera(
141 pan_target=(1.5, 0, 0), dimension=6, dimension_dir="width"
142)
143front_left_top_camera = FrontLeftTopCamera(
144 pan_target=(1.5, 0, 0), dimension=6, dimension_dir="width"
145)
146rear_left_top_camera = RearLeftTopCamera(pan_target=(1.5, 0, 0), dimension=6, dimension_dir="width")
147
148cameras_geo = [
149 top_camera,
150 side_camera,
151 rear_camera,
152 bottom_camera,
153 front_left_bottom_camera,
154 rear_right_bottom_camera,
155]
156
157
158limits_cp = [(-1, 1), (-1, 1), (-1, 1), (-0.3, 0), (-0.3, 0), (-1, 1), (-1, 1), (-1, 1)]
159cameras_cp = [
160 front_camera,
161 front_left_top_camera,
162 side_camera,
163 rear_left_top_camera,
164 rear_camera,
165 bottom_camera,
166 front_left_bottom_camera,
167 rear_right_bottom_camera,
168]
169
170
171avg = Average(fraction=0.1)
172CD = DataItem(data="surface_forces/totalCD", exclude=exclude, title="CD", operations=avg)
173
174CL = DataItem(data="surface_forces/totalCL", exclude=exclude, title="CL", operations=avg)
175
176CDA = DataItem(
177 data="surface_forces",
178 exclude=exclude,
179 title="CD*area",
180 variables=[Variable(name="area", data="params.reference_geometry.area")],
181 operations=[Expression(expr="totalCD * area"), avg],
182)
183
184CLf = DataItem(
185 data="surface_forces",
186 exclude=exclude,
187 title="CLf",
188 operations=[Expression(expr="1/2*totalCL + totalCMy"), avg],
189)
190
191CLr = DataItem(
192 data="surface_forces",
193 exclude=exclude,
194 title="CLr",
195 operations=[Expression(expr="1/2*totalCL - totalCMy"), avg],
196)
197
198CFy = DataItem(data="surface_forces/totalCFy", exclude=exclude, title="CS", operations=avg)
199
200statistical_data = [
201 "params/reference_geometry/area",
202 CD,
203 CDA,
204 Delta(data=CD),
205 CL,
206 CLf,
207 CLr,
208 CFy,
209 "volume_mesh/stats/n_nodes",
210 "params/time_stepping/max_steps",
211]
212statistical_table = Table(
213 data=statistical_data,
214 section_title="Statistical data",
215 formatter=[
216 (
217 None
218 if d
219 in [
220 "params/reference_geometry/area",
221 "volume_mesh/stats/n_nodes",
222 "params/time_stepping/max_steps",
223 ]
224 else ".4f"
225 )
226 for d in statistical_data
227 ],
228)
229
230
231geometry_screenshots = [
232 Chart3D(
233 section_title="Geometry",
234 items_in_row=2,
235 force_new_page=True,
236 show="boundaries",
237 camera=camera,
238 exclude=exclude,
239 fig_name=f"geo_{i}",
240 )
241 for i, camera in enumerate(cameras_geo)
242]
243
244cpt_screenshots = [
245 Chart3D(
246 section_title="Isosurface, Cpt=-1",
247 items_in_row=2,
248 force_new_page=True,
249 show="isosurface",
250 iso_field="Cpt",
251 exclude=exclude,
252 camera=camera,
253 )
254 for camera in cameras_cp
255]
256
257cfvec_screenshots = [
258 Chart3D(
259 section_title="CfVec",
260 items_in_row=2,
261 force_new_page=True,
262 show="boundaries",
263 field="CfVec",
264 mode="lic",
265 limits=(1e-4, 10),
266 is_log_scale=True,
267 exclude=exclude,
268 camera=camera,
269 )
270 for camera in cameras_cp
271]
272
273y_slices_lic_screenshots = [
274 Chart3D(
275 section_title=f"Slice velocity LIC y={y}",
276 items_in_row=2,
277 force_new_page=True,
278 show="slices",
279 include=[f"slice_y_{name}"],
280 field="velocityVec",
281 mode="lic",
282 limits=(0 * u.m / u.s, 50 * u.m / u.s),
283 camera=side_camera_slice,
284 fig_name=f"slice_y_vec_{name}",
285 )
286 for name, y in zip(["0", "0_2", "0_4", "0_6", "0_8"], [0, 0.2, 0.4, 0.6, 0.8])
287]
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]
303
304
305y_slices_lic_screenshots = [
306 Chart3D(
307 section_title=f"Slice velocity LIC y={y}",
308 items_in_row=2,
309 force_new_page=True,
310 show="slices",
311 include=[f"slice_y_{name}"],
312 field="velocityVec",
313 mode="lic",
314 limits=(0 * u.m / u.s, 50 * u.m / u.s),
315 camera=side_camera_slice,
316 fig_name=f"slice_y_vec_{name}",
317 )
318 for name, y in zip(["0", "0_2", "0_4", "0_6", "0_8"], [0, 0.2, 0.4, 0.6, 0.8])
319]
320
321z_slices_screenshots = [
322 Chart3D(
323 section_title=f"Slice velocity z={z}",
324 items_in_row=2,
325 force_new_page=True,
326 show="slices",
327 include=[f"slice_z_{name}"],
328 field="velocity",
329 limits=(0 * u.m / u.s, 50 * u.m / u.s),
330 camera=top_camera_slice,
331 fig_name=f"slice_z_{name}",
332 )
333 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])
334]
335
336y_plus_screenshots = [
337 Chart3D(
338 section_title="y+",
339 items_in_row=2,
340 show="boundaries",
341 field="yPlus",
342 exclude=exclude,
343 limits=(0, 5),
344 camera=camera,
345 fig_name=f"yplus_{i}",
346 )
347 for i, camera in enumerate([top_camera, bottom_camera])
348]
349cp_screenshots = [
350 Chart3D(
351 section_title="Cp",
352 items_in_row=2,
353 show="boundaries",
354 field="Cp",
355 exclude=exclude,
356 limits=limits,
357 camera=camera,
358 fig_name=f"cp_{i}",
359 )
360 for i, (limits, camera) in enumerate(zip(limits_cp, cameras_cp))
361]
362wall_shear_screenshots = [
363 Chart3D(
364 section_title="Wall shear stress magnitude",
365 items_in_row=2,
366 show="boundaries",
367 field="wallShearMag",
368 exclude=exclude,
369 limits=(0 * u.Pa, 5 * u.Pa),
370 camera=camera,
371 fig_name=f"wallShearMag_{i}",
372 )
373 for i, camera in enumerate(cameras_cp)
374]
375
376report = ReportTemplate(
377 title="Aerodynamic analysis of DrivAer",
378 items=[
379 Summary(),
380 Inputs(),
381 statistical_table,
382 Chart2D(
383 x="x_slicing_force_distribution/X",
384 y="x_slicing_force_distribution/totalCumulative_CD_Curve",
385 fig_name="totalCumulative_CD_Curve",
386 background="geometry",
387 exclude=exclude,
388 ),
389 Chart2D(
390 x="surface_forces/pseudo_step",
391 y="surface_forces/totalCD",
392 section_title="Drag Coefficient",
393 fig_name="cd_fig",
394 exclude=exclude,
395 focus_x=(1 / 3, 1),
396 ),
397 *geometry_screenshots,
398 *cpt_screenshots,
399 *y_slices_screenshots,
400 *y_slices_lic_screenshots,
401 *z_slices_screenshots,
402 *y_plus_screenshots,
403 *cp_screenshots,
404 *wall_shear_screenshots,
405 ],
406 settings=Settings(dpi=150),
407)
408
409report = report.create_in_cloud(
410 f"{size}-{len(cases)}cases-slices-using-groups-Cpt, Cpx, wallShear, dpi=default",
411 cases,
412 solver_version=SOLVER_VERSION,
413)
414
415report.wait()
416report.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.