Generate the shape coefficient (Ce)

The shape coefficient is the area-weighted mean of Cp over each zone of the body. Zones come from a 3D box grid; every triangle is assigned to a zone by its centroid. The v3 pipeline attaches the mesh, builds a zoning_grouping, then aggregates Cp per zone with an area_weighted_mean.

Set up a working directory

A pipeline template declares its inputs, ops and outputs with paths that are resolved relative to the template file. The shipped example templates live under fixtures/tests/pressure/templates/ and reference sibling ../data/ and ../galpao/ folders, so we copy the templates and the fixture data into a single scratch directory and run from there. In a real project you would instead point the template at your own data and run cfdmod run <template>.yaml directly.

[1]:
%matplotlib inline
import pathlib
import shutil
import tempfile


def find_fixtures() -> pathlib.Path:
    """Walk up from the current directory to locate the repo fixtures."""
    for base in [pathlib.Path.cwd(), *pathlib.Path.cwd().parents]:
        candidate = base / "fixtures" / "tests" / "pressure"
        if candidate.is_dir():
            return candidate
    raise FileNotFoundError("could not locate fixtures/tests/pressure")


fixtures = find_fixtures()
workdir = pathlib.Path(tempfile.mkdtemp(prefix="cfdmod_pressure_"))
for name in ("data", "galpao", "templates"):
    shutil.copytree(fixtures / name, workdir / name)
(workdir / "out").mkdir(exist_ok=True)

print("working directory:", workdir)
working directory: /tmp/cfdmod_pressure_cxeevg4i

Compute Cp first

The force, moment and shape coefficients all consume a Cp time series. We run the cp.yaml template first; it writes ./out/cp.time_series inside the working directory, which the template below reads back in.

[2]:
from cfdmod import load_template, run_template
from cfdmod.adapters.xdmf_h5 import XdmfH5Storage

storage = XdmfH5Storage(pathlib.Path("/"))
run_template(load_template(workdir / "templates" / "cp.yaml"), storage=storage)
print("Cp time series written to", workdir / "out")
Cp time series written to /tmp/cfdmod_pressure_cxeevg4i/out

The Ce pipeline template

zoning_grouping partitions the mesh into a x_intervals x y_intervals x z_intervals box grid; field_series_for_groups with agg: area_weighted_mean then collapses Cp to one series per zone.

[3]:
print((workdir / "templates" / "ce.yaml").read_text())
# Ce pipeline template (v3 schema).
#
# Per-zone shape coefficient via area-weighted-mean of Cp. Zones come
# from a 3D box grid (x_intervals x y_intervals x z_intervals);
# triangles are assigned by centroid.
name: ce_default

inputs:
  cp:
    kind: surface
    path: ./out/cp.time_series

pipeline:
  - id: cp_with_mesh
    kind: mesh_attach
    source: cp
    mesh: ../galpao/galpao.normalized.lnas

  - id: cp_zoned
    kind: zoning_grouping
    source: cp_with_mesh
    mesh: ../galpao/galpao.normalized.lnas
    x_intervals: [0.0, 125.0, 250.0]
    y_intervals: [0.0, 80.0, 170.0]
    z_intervals: [0.0, 10.0, 20.0]

  - id: ce
    kind: field_series_for_groups
    source: cp_zoned
    grouping: zone
    field: cp
    agg: area_weighted_mean
    out: ce

outputs:
  ce: {source: ce, path: ./out/ce.time_series}

Run the pipeline

[4]:
bindings = run_template(load_template(workdir / "templates" / "ce.yaml"), storage=storage)
sorted(bindings)
[2026-07-06 01:03:07,594] [INFO] - cfdmod - apply_groupings: 1 grouping(s) on mesh with 2915 triangle(s) and 20 surface(s)
[2026-07-06 01:03:07,597] [INFO] - cfdmod -   [0] ByZoningGrouping produced 8 group(s): ['0-0-0', '0-0-1', '0-1-0', '0-1-1', '1-0-0']...
[4]:
['ce', 'cp', 'cp_with_mesh', 'cp_zoned']

Per-zone shape coefficient

ce is a GroupsDataSource with one row per occupied zone. We show the time-averaged Ce per zone.

[5]:
import pandas as pd

from cfdmod.core.grouping import groups_in

ce = bindings["ce"]
pg = ce.parent_grouping
labels = [pg.label(g) for g in groups_in(pg)]
series = ce.fields.read("ce")
df = pd.DataFrame({"ce_mean": series.mean(axis=1)}, index=labels)
df.index.name = "zone"
df
[5]:
ce_mean
zone
0 -0.054599
1 0.058763
2 -0.399228
3 -0.187045
4 -0.048913
5 -0.198927
6 -0.254235
7 -0.440501
[6]:
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(7, 3.5))
ax.bar(df.index, df["ce_mean"])
ax.set_xlabel("zone")
ax.set_ylabel("mean Ce")
ax.set_title("Area-weighted mean Cp per zone")
plt.tight_layout()
plt.show()
../../../_images/use_cases_pressure_coefficients_calculate_Ce_11_0.png