Automotive Report

Contents

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 the Project interface.

  • Shows the configuration of diverse output types (SurfaceOutput, SliceOutput, IsosurfaceOutput) to capture data for detailed flow analysis and visualization within the report.