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