Release Notes¶
v2.0.1¶
Patch release on top of v2.0.0 covering the worked-notebook polish
and one Cm regression that surfaced the first time a user ran
run_cm with the new reference-mesh path on a real container pack.
Notebook (process_container_pack.ipynb)¶
The geometry-inspect and zoning auto-detect cells now load
REFERENCE_MESHwhen set, falling back to the body H5’s embedded geometry when not. Previously they always calledmesh_from_h5(BODY_H5), so when a user pointedREFERENCE_MESHat a fixed-frame STL the auto-detect partitioned the body H5’s wind-aligned frame and then applied those boundaries to the reference-frame Cp output – producing wrong region splits along whichever axis the wind was aligned with.Zoning detection is now hierarchical 2D. The previous implementation projected centroids on each axis independently, which lumped misaligned containers together along whichever axis had row overlap. New algorithm: pick the xy axis with the largest single gap as the primary split, then within each primary subset detect secondary boundaries on the other xy axis; the Cartesian
ZoningModel’s secondary intervals are the union of all per-subset boundaries. Misaligned rows now produce extra empty cells (harmless – the pipeline treats them as zero-area regions) but each container ends up in its own region.Z is no longer partitioned automatically;
z_intervalsis fixed at[-inf, +inf]. The container-pack use case has all containers on the ground; vertical splits are out of scope.The auto-detect cell now prints the resulting
x_intervals,y_intervals,z_intervalsso users can map regions to physical containers without inspecting the model object.
Pressure pipeline¶
_resolve_region_origin(incfdmod/pressure/functions.py) used to parse the integer prefix of a region label viaint(region_label.split("-", 1)[0]). For triangles whose centroid did not fall into any zoning cell –cfdmod.pressure.geometry.get_indexing_maskinitialises the region array to-1and only updates entries that match a cell – the resulting label was"-1-<body>", and the split produced an empty-string head, raisingValueError: invalid literal for int() with base 10: ''. Cf and Ce survived because they only group by the string label; only Cm parsed the integer back. Switched the parser torsplit("-", 1)so the body suffix is peeled from the right and negative ints survive:"-1-pack"->("-1", "pack")->int("-1"). Sentinel triangles now fall through to thelever_strategybranch (region_base computes a meaningful(mean_x, mean_y, min_z)from their own rows; fixed picks up the body’slever_origin). Three regression tests undertests/pressure/test_functions_cm.pypin the behaviour for fixed, region_base, and explicit-override on a negative key.
v2.0.0¶
API-first rewrite of the post-processing pipeline. The branch focus was “library that external scripts and notebooks can drive” – the public API, the I/O contract and the output layout were all redesigned around that goal.
Sane defaults and explicit knobs¶
The Cp config inputs were tightened so the pipeline never silently guesses something the user didn’t ask for. All of these are behaviour-changing relative to v1.x:
CpConfig.macroscopic_typenow defaults to"pressure"(was"rho"). The description lists both options. If the solver wrote real pressure – the common case – you can leave it unset.New
CpConfig.reference_pressure: Literal["probe", "average"], default"probe"."probe"uses the first probe point (the reference probe placed above the body, the standard wind-tunnel choice);"average"takes the spatial mean across all probe points at each timestep.New
CpConfig.normalize_time: bool = False. Time-axis normalisation is now opt-in: with the defaultFalse,/meta/time_normalizedcarries raw solver time (nothing is silently divided byL/U). Filters and statistics downstream operate in whichever units this setting selects.simul_characteristic_lengthbecomes meaningful only whennormalize_time=True;simul_U_Hstays a hard requirement (it is in the Cp dynamic-pressure denominator regardless).CfConfig.nominal_areaandCmConfig.nominal_volumeare now required (gt=0). The previous “fall back to tribute area / volume when zero” behaviour is gone – without an explicit reference value, the resulting Cf / Cm cannot be converted back to real-scale forces / moments unambiguously, so the program no longer chooses for you. The unreachable tribute-area / tribute-volume code paths intransform_Cf/transform_Cmwere removed.For Cf / Cm,
full_scale_U_Handfull_scale_characteristic_lengthonExtremeGumbelParamsModelare now optional. When omitted the runner reads them from the Cp metadata embedded incp_h5(/processing_metadata) – so you only need to specify those scales once, in the Cp scope. Cp itself still requires explicit values.
Reference-frame override (multi-direction sweeps)¶
run_cp(mesh_path=...) now actually embeds that mesh’s triangles +
vertices into the Cp output via process_xdmf_to_cp(mesh_override=...),
with a triangle-count safety check. Use case: same building, several
wind directions; each solver run produces a body H5 in its own wind-
aligned (“spun”) coordinate frame, and you want all cp / cf / cm / ce
outputs in a single fixed reference frame for cross-direction
comparison. Downstream run_cf / run_cm / run_ce already default
mesh from cp_h5, so the reference frame propagates without re-passing
mesh_path per call.
First-class filter chain¶
Signal-processing filters are now their own pipeline stage rather than being smuggled into the statistics block:
cfdmod.pressure.filters.apply_filters(input_h5, output_h5, filters=[...], group=...)reads any coefficient timeseries, applies the chain in order, and writes a new*.time_series.h5with the same on-disk shape (/Triangles + /Geometry,/{group}/t{T}per timestep,/meta/..., sibling temporal XDMF). The applied chain is recorded under/processing_metadataso the lineage is self- describing.Initial filter type:
MovingAverageFilter(window=...).windowis in the input file’s own time-axis units (raw solver time by default; convective time whennormalize_time=True). No implicit unit conversion. Implemented via a flat Pydantic discriminated union, so a new filter is one new class added to the union and one branch in_apply_one.ExtremeMovingAverageParamsModel,moving_average_extreme_values, and the"Moving Average"entry inExtremeMethodswere removed. Statistics now expose only the three real peak-factor methods:Absolute,Peak,Gumbel. Moving-average smoothing is done in the filter stage, then statistics run over the filtered file.
Pipeline (Cp / Cf / Cm / Ce)¶
Disk-first stats contract. Every coefficient persists its full per-triangle timeseries to an XDMF+H5 file before statistics are computed. Statistics are then read back from disk via
cfdmod.pressure.statistics_runner.calculate_statistics_from_h5so memory pressure no longer scales with the number of timesteps.Single combined
stats.{h5,xdmf}for the whole run, with an embedded mesh per leaf group.write_stats_xdmfwalks the H5 tree and emits one<Grid>per(coefficient, body[, direction[, case]])triple, each on the correct sub-mesh – fixes the silent length-mismatch behaviour in v1.x where Cf/Cm/Ce stats were written against the full-mesh topology while their values were per-body or per-region.Multi-attribute temporal XDMF for Cf/Cm body timeseries: pick
cf_x,cf_y,cf_z(orcm_x/y/z) from the ParaView Attribute selector on the same animation.The user’s input H5 files are read-only. The previous in-place mutator (
add_cp2xdmf) is gone, and a regression test pins body / probe size and modification-time across a fullrun_cp.Multi-format mesh resolver.
mesh_pathnow accepts.lnas/.stl/.h5/.xdmf(or a pre-loadedLnasFormat). It is also optional – when omitted, the geometry is read from the source H5’s embedded/Triangles + /Geometry. Internally LnasFormat is still the carrier; externally STL/XDMF/H5 are first-class.Embedded post-processing metadata. Every output H5 carries
<group>/processing_metadata/config.yamlplus group attrs forproduced_at,cfdmod_version,coefficient,cfg_lbl,body,direction, and the input paths.read_processing_metadata(path, group)round-trips back to a dict.Output layout is flat by default: every artefact for a
(coefficient, cfg_lbl[, body[, case]])triple sits directly inoutput_pathwith dot-separated filenames (cp.default.time_series.h5,Cf.containers.pack.time_series.h5, …). Combined stats land instats.{h5,xdmf}.
New Cm features¶
lever_strategy="region_base"derives a per-region base from each region’s triangle vertices(mean_x, mean_y, min_z)– the natural reference for overturning moments about the floor footprint.lever_strategy="region_bbox_corners_xy"expands one body into four independent runs (xmin_ymin,xmin_ymax,xmax_ymin,xmax_ymax) so external pipelines can scan worst-case overturning moments around every footprint corner without doing the orchestration themselves.region_lever_origins: dict[int, (x, y, z)]for explicit per-region centers (HFPI-style externally-known centers of mass).lever_origin_cases: dict[case_label, dict[region_int, (x, y, z)]]for arbitrary case scans.
Public API¶
from cfdmod import run_cp, run_cf, run_cm, run_ce– canonical pipeline entry points.from cfdmod import load_mesh, mesh_from_h5, read_processing_metadata, write_processing_metadata– IO helpers surfaced for external consumers.from cfdmod import read_timeseries_df, to_csv, plot_timeseries– pull apd.DataFrameout of any*.time_series.h5(with optional triangle / region / timestep filtering), export to CSV, or plot selected columns with one call.regions=Truededuplicates the per-triangle broadcast of Cf/Cm so you get one column per region instead of one per triangle.from cfdmod import MovingAverageFilter, apply_filters– the filter chain (see “First-class filter chain” above).FilterSpecis also exported for type annotations of user-built chains.cfg_pathaccepts either a YAML path or a pre-builtCpCaseConfig/CfCaseConfig/CmCaseConfig/CeCaseConfiginstance, so in-memory pipelines don’t need sidecar YAMLs.CLI subcommands (
python -m cfdmod pressure cp|cf|cm|ce) accept all mesh formats with--meshnow optional.
Breaking changes¶
cfdmod.pressure.add_cp2xdmfremoved (it mutated the input body H5). External callers should userun_cp, which writes to a separate output file.cfdmod.pressure.add_lever_arm_to_geometry_dfsignature changed: the third argument is now aMomentBodyConfiginstead of a barelever_origintuple, so the per-region lever logic can be applied without the caller re-implementing it.MomentBodyConfig.lever_originis now optional with default(0.0, 0.0, 0.0). Configs that previously relied on it being required will continue to load.cfdmod.pressure.path_manager.{get_results_h5_path, get_results_xdmf_path}renamed toget_stats_h5_path/get_stats_xdmf_path. The output files are nowstats.{h5,xdmf}(wasresults.{h5,xdmf}).cfdmod.apiandcfdmod.use_casesshims have been removed. v1.x scripts that imported via these paths must update to the top-level domain modules (cfdmod.ioand the per-domain packages such ascfdmod.loft,cfdmod.pressure,cfdmod.roughness, …).cfdmod.configandcfdmod.HashableConfighave been removed. The base class added ato_yaml()method that no caller used, ato_dict()method that was a one-line wrapper around Pydantic’s built-inmodel_dump(), and asha256()config-fingerprint helper that only one scratch notebook ever called. Configs now subclasspydantic.BaseModeldirectly. Migration:config.to_dict()->config.model_dump()config.to_yaml(path)-> serialise via your YAML library of choice (e.g.ruamel.yaml.YAML().dump(config.model_dump(), fh))config.sha256()-> compute it externally:hashlib.sha256(config.model_dump_json().encode()).hexdigest()The per-*CaseConfigfrom_file(path)classmethods are unchanged.
Pre-typer argparse entry points were removed:
cfdmod.loft.main,cfdmod.roughness.main, andcfdmod.altimetry.main. Each module now exposes a typer app atcfdmod.<module>.cli:app, registered under the unifiedpython -m cfdmod <module>entry point.cfdmod.snapshot.__main__was removed (it imported a non-existentcfdmod.snapshot.main). The snapshot module currently has no CLI entry point; use the Python API directly until one lands.CpConfig.macroscopic_typedefault flipped from"rho"to"pressure". Configs that rely on the implicit default but actually feed LBM density now need to setmacroscopic_type="rho"explicitly.CpConfigtime-axis normalisation is now opt-in vianormalize_time: bool = False. The previous behaviour (always divide byL/U) becomesnormalize_time=True. With the new default,/meta/time_normalizedcarries raw solver time, and filter / Gumbel windows operate in the same raw-time units.CfConfig.nominal_areaandCmConfig.nominal_volumeare now required (gt=0); the implicit-tribute fallback was removed. Configs that previously left them unset (or set them to0) need to provide an explicit reference area / volume. Onlytransform_Cf/transform_Cmwere affected; the publicget_representative_areas/get_representative_volumehelpers are still exported.ExtremeMovingAverageParamsModel,cfdmod.pressure.functions.moving_average_extreme_values, and the"Moving Average"entry inExtremeMethodswere removed. To get stats over a moving-average-smoothed signal, runapply_filters([MovingAverageFilter(window=...)])first and then run statistics over the filtered file.The pressure pipeline is a single chain now:
run_cp-> optionalapply_filters->run_cf/run_cm/run_ce-> stats merged intostats.{h5,xdmf}. The in-memory transform helpers (process_xdmf_to_cp,process_Cf,process_Cm,process_Ce,process_timeseries,process_surfaces,transform_Cf,transform_Cm,transform_Ce,calculate_statistics,tabulate_geometry_data,combine_stats_data_with_mesh,add_lever_arm_to_geometry_df,get_indexing_mask,get_representative_areas,get_representative_volume,get_surface_dict) and the data-class containers (GeometryData,ProcessedEntity,CommonOutput,CeOutput) are no longer part of the public API. They remain reachable ascfdmod.pressure.functions.*/cfdmod.pressure.geometry.*for users who need them in tests, but onlyrun_*,apply_filters,calculate_statistics_from_h5, the filter / config types, and the zoning / body / statistics models are exposed fromcfdmod.pressureand the top-level package. Same goes for thecfdmod.process_Cf/process_Cm/process_Cetop-level re-exports that used to exist; those are gone.The
cfdmod.analysispackage was removed. Inflow lives in a single top-level module now,cfdmod.inflow. Migration:from cfdmod.analysis.inflow.profile import InflowData, NormalizationParameters->from cfdmod.inflow import InflowData, NormalizationParametersfrom cfdmod.analysis.inflow.functions import calculate_mean_velocity, ...->from cfdmod.inflow import calculate_mean_velocity, ...The top-levelfrom cfdmod import InflowData, NormalizationParametersre-export is unchanged.
Compatibility / migration¶
Legacy pandas-HDFStore inputs from inflow (
cfdmod.inflow) and HFPI (cfdmod.hfpi.static.read_static_forces) are read with aDeprecationWarning; the readers prefer the new layout but accept the old one.cfdmod.pressure.migrate.migrate_body_h5andmigrate_probe_h5convert legacy pandas-HDFStore body/probe files to the new XDMF+H5 layout on disk for users who want to upgrade their fixtures.aerosim-lnasupgraded to>=0.6.9,<0.7.
Documentation / tooling¶
Top-level
notebooks/process_container_pack.ipynbis the worked example: readsbodies.body_cp body.h5+points.point_cp ref.h5from the repo root, auto-detects container partition via a >1 m gap rule, runs Cp/Cf/Cm end-to-end withlever_strategy="region_base"(footprint-base lever) by default, and never authors a surface label (geometry is read straight from the body H5). Cf and Cm runs in the notebook are configured forxandyonly – the most common client-facing case.cfdmod.notebook_utilsprovidesmesh_summary,show_config,load_lnasfor exploratory notebook work.ASCII-only convention is now project-wide (CLAUDE.md): no em-dash, ellipsis, arrow glyphs, typographic quotes, or other non-ASCII characters in source, configs, notebooks or docs. Use
--,...,->,'/"instead. The codebase was swept to match.
Tests / quality¶
pytest markers for the suite:
unit(pure-function, fast),integration(multi-component end-to-end), andperf(synthetic big-data benchmarks). Default invocation excludesperf; run it explicitly withpytest -m perf.Shared pressure conftest centralises fixture paths, config builders (
make_cp_cfg,make_cf_cfg,make_cm_cfg), zoning helpers and stats walkers, replacing the per-test boilerplate.Performance harness (
tests/pressure/test_perf.py) synthesises body + probe data in-fixture (no dependency on root files) and drives the full Cp / Cf / Cm chain at two scales: medium (~30k triangles x 2k timesteps) and extreme (~150k triangles x 10k timesteps, 1/5x of the worst real-world case).Per-run perf report: tracemalloc-tracked Python heap peak +
getrusageRSS for each scale, written tooutput/perf/perf_report.mdandperf_report.jsonfor tracking regressions across releases.
Dependencies / packaging¶
Runtime deps slimmed:
tablesandfilelockremoved from the base install.tablesis still needed for the legacy pandas-HDFStore compat readers incfdmod.inflow,cfdmod.hfpi.static, andcfdmod.pressure.migrate; install via the newlegacyextras (pip install aerosim-cfdmod[legacy]).geometryextras now ships onlytrimesh.pymeshlabwas removed entirely – it is GPL-licensed and would force GPL on any downstream code linking it. Code paths that genuinely need pymeshlab are documented and the user installs it explicitly at their own license risk.docsextras switched fromsphinx-book-themetoshibuya(modern theme, active upstream).cfdmod.ionow lazy-loads its vtk-backed helpers via PEP 562__getattr__.import cfdmodno longer pulls VTK at load time; accessing a vtk-backed name without thevtkextras raises a clearImportErrorpointing atpip install aerosim-cfdmod[vtk].cfdmod.io.vtk.*imports VTK classes viavtkmodules.*submodules (e.g.from vtkmodules.vtkIOXML import vtkXMLPolyDataWriter) instead of the catch-allimport vtk, avoiding an unnecessary load of the full VTK universe on first use.Runtime dep floors bumped to the modern stack:
numpy>=2.0,scipy>=1.13,pandas>=2.2,pydantic>=2.10,matplotlib>=3.9. Out-of-cap upper bounds relaxed to allow the current latest:ruamel-yaml<0.20,pyarrow<25,myst-parser<6,ipython<10,ipykernel<8,pyvista<0.50. Dev tooling brought to current majors:pytest>=9,black>=26,isort>=8,ruff>=0.15,tox>=4.53.requires-pythonstays at>=3.10; bump it (andtables) when 3.10 support is dropped.
v1.1.2¶
Automated CI/CD workflow
v1.1.1¶
Coefficient time series are now in normalized time scales. Time values from the solver are normalized by the CST value in the solver time scale.
Input body pressure data is normalized by the CST value
Derived coefficients also use a normalized time scale
Parameters for statistical model are in full scale and need to be normalized using full scale CST.
v1.1.0¶
It features the refactor for pressure use case module.
Updated time series format to matrix form
Changed how direction logic is applied for Cf and Cm
Updated statistics functions and models
v1.0.0¶
First production stable release. It features all consulting use cases: