Release Notes¶
3.3.0¶
Additive feature work on top of v3.2.0. Facade Cp snapshots return as a config-driven ParaView/VTK stage, snapshot dependencies become an opt-in extra, and a consulting post-processing template is added. No public v3 symbol is removed or changed in signature; the only removal is the 3-D matplotlib mesh-field renderer described below.
Facade Cp snapshots (cfdmod.snapshot)¶
New config-driven facade snapshot builder (
building_facade) renders the four walls side by side plus the roof, split into per-floor bands, from abuilding_facade_config. This replaces the removed 3-D matplotlib mesh-field renders as the facade Cp deliverable.A standalone runner is provided under
examples/facade_snapshot/.
Optional snapshot extra¶
Snapshot tooling no longer imports IPython at module load. The heavier snapshot dependencies are now an opt-in
[snapshot]extra (pip install aerosim-cfdmod[snapshot]), so the base install stays lean for consumers that do not render snapshots.
Consulting post-processing template¶
Added an
examples/consulting_template/suite (inflow validation and static wind-load stages) that writes engineer-facing deliverables and uses shared, symmetric plot axes.Static-load deliverables are now emitted in labelled SI units and in tonne-force (tf) consistently, correcting the earlier dimensionalization.
The inflow stage falls back to an available inlet run when the preferred run is absent.
Dynamics plotting (cfdmod.dynamics)¶
Floor-load plots use symmetric, shared axes with global per-floor x-limits, and wind-direction labels use a degree symbol, for directly comparable figures across floors and directions.
Mesh-field rendering (cfdmod.mesh_field)¶
Removed the per-triangle 3-D matplotlib mesh-field renderer (
triangle_field_figure) and its facade-selection helpers (facade_groups,facade_index_per_triangle). The 3-D triangle renders were illegible for tall or slender buildings – the equal-aspect box collapsed a slender tower to a sliver, and a near-planar facade viewed face-on through a 3-D camera collapsed to a thin line – so they were not a usable deliverable.The facade Cp-snapshot post-processing stage was dropped from the example suites (high-rise + consulting template) pending a rebuild on the
cfdmod.snapshot(ParaView/VTK) tooling driven by a snapshot config. The general mesh-field utilities are unchanged: line sampling (sample_field_along_line/moving_average_stats), the PyVista plane-slice render (slice_field_on_plane/render_plane_slice) and the whole-body PyVista snapshot (write_field_vtp/render_vtp_snapshot).
3.2.0¶
Additive feature work on top of v3.1.0: a building wind-load
post-processing layer (cfdmod.building), structural-model importers
(cfdmod.dynamics), and mesh-field rendering (cfdmod.mesh_field). No
public v3 symbol is removed or changed in signature.
Building wind-load post-processing (cfdmod.building)¶
BuildingCaseaggregates a case’s inputs (pressure timeseries, dynamic pressure, geometry) andcp_from_pressurederives Cp from a raw pressure signal, feeding the per-floor and dynamic stages.Per-floor
cf_per_floor/cm_per_floornormalise with an explicit reference area / volume (nominal_area/nominal_volume), not the legacy per-region bounding-box area.Peak extraction is selectable: raw maximum, gust
gust_peak_factor, or a Gumbel fit (PeakMethod).Dynamic response:
solve_building_response,floor_accelerationsandpeak_response_tablerun the modal SDOF solver over a per-floor load source and report peak floor accelerations.Occupant comfort:
comfort_limitchecks peak accelerations against NBR 6123, Melbourne (1992) and NBCC limits (withnbr6123_,melbourne1992_andnbcc_acceleration_limitexposed directly, plusmilli_g_to_mps2/mps2_to_milli_gconversions). The NBCC commercial limit is 25 milli-g.Design load cases:
generate_load_cases/save_load_case_tableswrite per-floor Fx / Fy / Mz peak/min/max load-case tables to CSV.Multi-direction fan-out:
FanoutPlan+run_fanoutdrive the whole per-floor / dynamic / comfort chain over every (direction, body, config) combination of a parametric study, withdirectional_envelopesand thejoin_by_*/filter_by_*reducers for cross-direction results.
Structural model import (cfdmod.dynamics)¶
read_tqs_portels(TQS Portico Espacial, nodal),read_tqs_portico(TQS per-floor) andread_eberick(AltoQi Eberick) convert design-tool modal exports into the internalBuildingStructuralDatathe dynamic recipe consumes, removing a manual transcription step.Nodal TQS data is aggregated to per-floor lumped mass / inertia / centre of mass / rigid-diaphragm mode shape (
aggregate_to_building); units are converted to SI (EberickUnits) and mode shapes are mass-normalized to unit generalized mass.BuildingStructuralDataround-trips throughmodes.csv/floors.csv/phi{m}.csv(from_csvs) and is writable from the command line (cfdmod dynamics <export_dir> --out ... --format tqs|portico|eberick).See the Structural Model Import use-case page for the file formats.
Mesh-field rendering (cfdmod.mesh_field)¶
Per-triangle matplotlib mesh-field renders (
triangle_field_figure) and facade Cp snapshots, plus a PyVista plane-slice field render (render_plane_slice/slice_field_on_plane) and line sampling (sample_field_along_line) for cutting through a volume field.moving_average_statsreuses the coremoving_averageop.
Output freshness / incremental runs¶
Each output a template writes is stamped with a signature hashed over the params and wiring of the steps that feed it, a change-detecting digest of the input files it depends on, and a code/format version tag.
output_status(template, storage)reports each declared output asfresh,stale, ormissingwithout running anything;cfdmod status <template>prints the same and exits non-zero if anything is stale (usable as a CI / Makefile gate).run_template(..., skip_fresh=True)(andcfdmod run --skip-fresh) skips recomputing outputs that are already up to date and runs only the steps the stale outputs depend on, so touching one late stage no longer recomputes fresh upstreams.The input-digest strategy is configurable (template
freshness.digestor--digest):size_mtime(default, no byte reads),content(a strong hash), orbackend(the backend’s native token).Storagegainsdigest/read_signature/write_signature; the default run path is unchanged for backends that do not implement them.
v3.1.0¶
Minor release. Turns the v3 core from “a library you call” into “a
contract a service can reflect on and drive from a UI”,
additively – no public symbol is removed or changed in signature, and
every new error subclasses the builtin it replaced, so existing
except (KeyError, ValueError) handlers keep working.
Op catalog and introspection¶
The op registry is now populated at import, so
OP_REGISTRYis non-empty without first running a template.New
list_ops()/op_info(kind)return each op’s arity, family, data-source contract, and parameter JSON Schema – enough for a node-based pipeline editor to render a form and validate a connection.Ops declare a machine-readable contract on
OpParams(consumes/produceskinds,requires_element_meta/produces_element_meta,replaces_fields,consumed_fields()/produced_fields()).Custom ops registered via
register_opare first-class: usable in templates, validated, and listed bylist_ops.
Static template validation¶
validate_templatenow runs a symbolic contract pass over the op catalog before any I/O – catching graph-wiring mistakes such as aforce_contributionbeforemesh_attach, or a surface-only op fed a points binding. Strict on kinds / element metadata, permissive on fields when the input’s fields were not declared. All shipped cp/cf/cm/ce templates still validate.
Dependency-light import¶
import cfdmod– and importing the template schema / op catalog undercfdmod.core– no longer pulls the heavy scientific stack (h5py, matplotlib, pandas, pyarrow, vtk, trimesh). They load lazily, only when a symbol that needs them is first accessed. A web / API layer can build and validate templates without the numeric stack installed.
Typed errors¶
New
CfdmodErrorhierarchy:TemplateError(aValueError),TemplateReferenceError(aKeyError),OpError(aRuntimeErrorcarryingstep_id/op_kind), andStorageKeyError(aKeyError).run_templatewraps op-execution failures inOpError.
Object-store storage¶
New
BlobStoreprotocol andXdmfH5BlobStoragerun the same pipeline against any blob backend (an object store such as S3, a DB blob column) by reusing the exact XDMF+H5 byte layout – cfdmod stays free of any cloud SDK.MemoryBlobStoreis provided for tests and in-process pipelines.
v3.0.0¶
Major release. Introduces the v3 data-source paradigm and
hard-removes the v2 pressure module. All Cp/Cf/Cm/Ce work now flows
through pipeline-as-YAML templates executed via cfdmod run <template.yaml> or the Python run_template API. This is a breaking
change: the v2 cfdmod.pressure package and its ~20 top-level exports
(CpCaseConfig, run_cp, MovingAverageFilter, apply_filters,
ZoningModel, …) are gone.
v3 foundation¶
Frozen
DataSource(five kinds), affineTimeAxis,Topology,Container, and theFieldStore/Storageprotocols with in-memory and XDMF+H5 adapters.Time ops and the
moving_averagefield op; field-algebra wrappers,compute_statistics, and grouping ops.modal_projection/modal_recompositionand the dynamic-analysis recipe with an injected modal solver.S1, pedestrian comfort, probe extraction, profile interpolation, and 1-D algebra.
Cp/Cf/Cm/Ce expressed as composable recipe helpers and shipped as example templates under
fixtures/tests/pressure/templates/.
YAML pipeline + CLI¶
cfdmod.core.pipeline_yaml: parse a steps array, walk inputs -> pipeline -> outputs, and dispatch each step into the op registry (step-id cross references, binary-op rhs resolution, path-like-field resolution against the templateroot:).cfdmod run <template.yaml>CLI entry point.Four introductory tutorial notebooks (
01_data_sources,02_recipes,03_pipelines,04_containers) that run end-to-end on synthetic data.
Migration¶
v2 |
v3 equivalent |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
See the v3 migration guide for the full mapping.
v2.1.0¶
Minor release on top of v2.0.1. Three geometry-preprocessing
subpackages land – the triangle-grouping pipeline
(cfdmod.geometry.grouping), the regroup module (cfdmod.regroup),
and the remesh module (cfdmod.remesh) – all exported from the
top-level cfdmod package and driven from the new notebooks. The
release also relaxes the dependency version bounds so aerosim-cfdmod
can be co-installed as a library without forcing resolution conflicts
on consumers. No breaking changes to existing public APIs; the legacy
sub_bodies YAML form and the v2.0 Cp/Cf/Cm/Ce output layout are
unchanged.
Triangle-grouping pipeline (cfdmod.geometry.grouping)¶
A new top-level subpackage promotes triangle grouping to a
first-class pipeline step, mirroring the v2.0 filter pipeline for
time-series. Specs are Pydantic models in a discriminated union
(GroupingSpec), composed left-to-right with apply_groupings, and
a triangle may belong to zero, one, or many groups. Three kinds ship
out of the box:
BySurfaceGrouping– collect named LNAS surfaces into one or more groups (generalises the legacyBodyDefinition.surfacesandCeConfig.sets).ByZoningGrouping– axis-aligned centroid binning into a Cartesian grid of regions (generalisesZoningModel).ByDivisionsGrouping– convenience wrapper overByZoningGroupingthat splits each axis inton_div_{x,y,z}equal cells over the candidate centroid bounding box.BySizeGrouping– convenience wrapper that builds cells of a fixedsize_{x,y,z}per axis anchored at the bounding-box minimum.ByConnectivityGrouping– one group per connected component of the (sub)mesh, defined by shared-edge adjacency. The first kind the legacysub_bodiesfield could not express.ByNormalGrouping– bucket triangles by the cardinal direction their outward normal best aligns with (windward / leeward / roof / sidewall in one shot, with a tolerance angle).ByPlaneGrouping– bin centroids by signed distance from an oriented plane. GeneralisesByZoningGroupingto non-axis-aligned half-space splits; the default produces two halves on either side of the plane.ByPercentileGrouping– equal-count quantile bins along one axis, for stable per-cell statistics when triangle density is uneven.ByCylindricalGrouping– Cartesian product of (r, theta, axial) bins around a user-chosen cylinder axis. Natural fit for towers, silos, chimneys and any partition that prefers cylindrical coordinates over Cartesian.CustomGrouping– escape hatch for grouping logic the built-in kinds cannot express: user supplies a Python callback (importable dotted path or a direct callable) plus an opaqueparamsdict, and the callback returns the standard{group_name: triangle_indices}mapping. Callback signature is fixed ((mesh, candidate_idxs, params) -> dict); the driver validates indices are in range and honorrestrict_to. Dotted-path callbacks round-trip cleanly throughdump_groupings/load_groupings; direct callables are serialised by deriving their import path when stable (lambdas and local functions raise on serialise).
Each spec exposes a restrict_to: list[str] | None field so later
steps can scope their work to triangles already in earlier groups;
this is how the legacy surface -> sub_body nesting is reproduced.
BodyConfig (and MomentBodyConfig) gain an opt-in
groupings: list[GroupingSpec] | None field. When set, it replaces
the implicit [BySurface, ByZoning(sub_bodies)] chain entirely;
mixing it with a non-default sub_bodies is rejected. The legacy
YAML form (sub_bodies only) keeps working untouched – the
canonical chain is synthesized internally via
BodyConfig.resolved_groupings(sfc_list).
Internally, cfdmod/pressure/geometry.py was refactored to consume
GroupingResult end-to-end. GeometryData.zoning_to_use is gone;
the new fields are grouping, spec_chain, and body_label.
Region labels ("{idx}-{body}" and the "-1-{body}" sentinel for
unbinned triangles) are byte-identical with the legacy output, so
existing downstream consumers, notebooks, and process_container_pack
runs are unaffected.
CeConfig.zoning keeps its current per-surface, normal-axis-aware
shape; promoting it onto the same explicit-chain pattern is tracked
as a follow-up.
A grouping chain may be persisted alongside the existing filters
chain in processing_metadata:
write_processing_metadata(h5, "/", {
"filters": [spec.model_dump() for spec in filter_chain],
"groupings": dump_groupings(grouping_chain),
})
See fixtures/tests/pressure/Cf_params_groupings.yaml for the new
explicit YAML form, and the API reference for the full surface.
Regroup module (cfdmod.regroup)¶
A standalone preprocessing module that takes a geometry plus a per-triangle HDF5 timeseries (Cp-style: rows = timesteps, columns = parent triangle ids), applies a chain of triangle-grouping specs (Ce-style 90-degree zoning cuts, connectivity-based container separation, etc.), and writes two aligned outputs:
a new
LnasFormatmesh with one named surface per group, anda new HDF5 timeseries whose columns line up with the new triangle order (
per_trianglemode) or whose values are area-weighted aggregates per group broadcast over each group’s triangles (area_weighted_meanmode).
It reuses cfdmod.geometry.grouping for all binning kinds and adds
one regroup-local spec, BySizeRoundedPerComponent, that fans out
per-component target-size subdivisions (resolved by run_regroup).
The module follows the standard domain layout (config / cli / run /
functions) and is runnable as python -m cfdmod.regroup. New public
symbols exported from the top-level package: RegroupConfig,
RegroupSpec, RegroupIndex, BySizeRoundedPerComponent,
build_regroup_mapping, build_regrouped_mesh,
apply_regroup_to_timeseries, expand_regroup_chain, run_regroup.
These fold slice + per-face aggregation + remesh into a single
in-memory pipeline.
Remesh module (cfdmod.remesh)¶
Per-group triangle coarsening for grouped LnasFormat meshes. The
default path is exact coplanar-fan merging: within each group,
adjacent triangles that share a plane are replaced by the minimum
triangulation of their joined region (a flat NxN-subdivided square
collapses to 2 triangles). It is lossless, deterministic, and pulls
in no external dependency. An opt-in path runs Quadric Error Metrics
(QEM) decimation on top via fast-simplification (MIT-licensed,
available through the new remesh extra) for groups on curved
surfaces where coplanar merge cannot reduce the count further.
API surface only – no CLI, no YAML config, no HDF5 I/O – intended
for in-process use from notebooks and debugging scripts. New public
symbols exported from the top-level package: merge_coplanar,
decimate_qem, remesh_per_group.
Relaxed dependency bounds¶
The runtime dependency pins were loosened from capped ranges to
floors-only so aerosim-cfdmod can be co-installed as a library
without forcing version-resolution conflicts on downstream projects
(for example one that needs pandas 3.x). The upper caps were dropped
from numpy, scipy, pandas, pydantic, ruamel-yaml,
colorama, matplotlib, pyarrow, and aerosim-lnas, and from the
geometry, remesh, and vtk optional extras. Lower bounds (the
minimum tested versions) are unchanged. The legacy/dev tables
pin keeps its <4 cap, which guards Python 3.10 support. Consuming
applications should pin exact versions themselves; this library only
declares floors.
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¶
examples/container_pack/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: