Examples
Four scripts that exercise the cfdtwin API end-to-end against PyFluent's
mixing_elbow.cas.h5 case. Source for each is inlined below, or available in
docs/examples/
on GitHub.
Requirements: Python 3.10+,
pip install cfdtwin, and a working ANSYS Fluent installation. Each script downloads the case via PyFluent's example helper on first run; subsequent runs reuse the local cache.
quickstart.py
Smallest end-to-end pipeline: project → DOE → 20 simulations → train → predict.
"""Smallest end-to-end cfdtwin pipeline.
Downloads PyFluent's mixing_elbow case, builds a 20-sample DOE, runs the
sims, trains a surrogate, and predicts at one new design point.
"""
from pathlib import Path
import cfdtwin
from ansys.fluent.core import examples
# Get the case file (cached after first download)
case_file = examples.download_file("mixing_elbow.cas.h5", "pyfluent/mixing_elbow")
# Project lives in ./quickstart_study/ — re-run won't clobber if it exists
project_path = Path("quickstart_study")
if (project_path / "project_info.json").exists():
project = cfdtwin.Project.open(project_path)
else:
project = cfdtwin.Project.create(project_path, name="quickstart")
project.set_case_file(case_file)
project.set_inputs({
"cold-inlet|momentum > velocity_magnitude": (0.2, 0.6),
"hot-inlet|momentum > velocity_magnitude": (0.4, 1.2),
})
project.set_outputs([
{"name": "outlet", "category": "Surface",
"field_variables": ["temperature"]},
])
project.generate_doe(n=20, method="lhs", seed=42)
# mixing_elbow.cas.h5 is a single-precision case file.
project.connect_fluent(precision="single")
project.run_simulations(verbose=True) # prints "sim X/N: status" lines
result = project.train(model_name="run1")
print()
print(result.summary())
pred = project.predict("run1", {
"cold-inlet|momentum > velocity_magnitude": 0.4,
"hot-inlet|momentum > velocity_magnitude": 0.8,
})
print(f"\nPredicted field shape: {pred.values.shape}")
print(f"Mean predicted temperature: {pred.values.mean():.2f} K")
full_workflow.py
Same pipeline as quickstart with verbose prints between stages, a batch-prediction sweep, and notes on the resulting disk layout.
"""Full mixing_elbow workflow with verbose output and result inspection.
Same pipeline as quickstart.py — extra prints between stages so you can see
what cfdtwin is doing at each step.
"""
from pathlib import Path
import cfdtwin
from ansys.fluent.core import examples
# --- 1. Get the case file -------------------------------------------------
case_file = examples.download_file("mixing_elbow.cas.h5", "pyfluent/mixing_elbow")
print(f"Case file: {case_file}\n")
# --- 2. Project setup -----------------------------------------------------
project_path = Path("elbow_study")
if (project_path / "project_info.json").exists():
project = cfdtwin.Project.open(project_path)
print(f"Reopened project: {project.name}")
else:
project = cfdtwin.Project.create(project_path, name="elbow_v1")
print(f"Created project: {project.name}")
project.set_case_file(case_file)
# Two velocity inlets, one surface output. set_inputs is declarative — no
# Fluent connection required at this stage.
project.set_inputs({
"cold-inlet|momentum > velocity_magnitude": (0.2, 0.6),
"hot-inlet|momentum > velocity_magnitude": (0.4, 1.2),
})
project.set_outputs([
{"name": "outlet", "category": "Surface",
"field_variables": ["temperature"]},
])
print("Inputs and outputs declared.\n")
# --- 3. DOE ---------------------------------------------------------------
n_samples = project.generate_doe(n=20, method="lhs", seed=42)
print(f"DOE: {n_samples} LHS samples\n")
# --- 4. Run sims ----------------------------------------------------------
# mixing_elbow.cas.h5 is a single-precision case file.
project.connect_fluent(precision="single")
sim_result = project.run_simulations(iterations=100, verbose=True)
print()
print(sim_result.summary())
if sim_result.failed:
print(f"Failed sim IDs: {sim_result.failed_ids}")
print()
# --- 5. Train -------------------------------------------------------------
train_result = project.train(model_name="elbow_v1", epochs=300)
print()
print(train_result.summary())
print()
best = train_result.best_model()
print(f"Best sub-model: {best.model_name}")
print(f" test R²: {best.test_metrics.r2:.4f}")
print(f" test RMSE: {best.test_metrics.rmse:.4f}")
print(f" POD modes: {best.n_modes}")
print(f" variance: {best.variance_explained:.4f}")
print()
# --- 6. Predict at a new design point -------------------------------------
pred = project.predict(train_result.model_name, {
"cold-inlet|momentum > velocity_magnitude": 0.4,
"hot-inlet|momentum > velocity_magnitude": 0.8,
})
print(f"Predict result:")
print(f" values shape: {pred.values.shape}")
print(f" coordinates shape: {pred.coordinates.shape if pred.coordinates is not None else 'None'}")
print(f" mean temperature: {pred.values.mean():.2f} K")
print(f" range: [{pred.values.min():.2f}, {pred.values.max():.2f}] K")
# --- 7. Batch predict (a sweep across hot-inlet velocity) -----------------
sweep = [
{"cold-inlet|momentum > velocity_magnitude": 0.3,
"hot-inlet|momentum > velocity_magnitude": v}
for v in [0.5, 0.7, 0.9, 1.1]
]
sweep_pred = project.predict(train_result.model_name, sweep)
print(f"\nSweep result: {sweep_pred.values.shape} (4 design points)")
for inp, vals in zip(sweep_pred.inputs, sweep_pred.values):
v = inp["hot-inlet|momentum > velocity_magnitude"]
print(f" hot-inlet vmag {v:.1f} -> mean outlet T = {vals.mean():.2f} K")
project.disconnect_fluent()
training_tuning.py
Trains three surrogate variants (defaults; fixed POD modes; variance cutoff
with a custom learning rate) and prints a side-by-side comparison. Reuses the
elbow_study/ project that full_workflow.py creates, so run that first.
"""Per-output training configuration: PCA modes, NN overrides.
Assumes you've already run quickstart.py or full_workflow.py to populate
the elbow_study project with simulation data. This script trains three
different model variants on the same data with different configurations
and compares them.
"""
from pathlib import Path
import cfdtwin
project_path = Path("elbow_study")
if not (project_path / "project_info.json").exists():
raise SystemExit(
"Run docs/examples/full_workflow.py first to populate elbow_study with sims."
)
project = cfdtwin.Project.open(project_path)
# Variant 1 — defaults (let cfdtwin pick POD modes by data shape)
print("\n=== Variant 1: defaults ===")
r1 = project.train(model_name="tune_defaults")
print(r1.summary())
# Variant 2 — fixed POD mode count
print("\n=== Variant 2: fixed 15 POD modes ===")
r2 = project.train(
model_name="tune_modes15",
outputs={
# The model_key is "<location>_<field>"
"outlet_temperature": {"pod": {"modes": 15}},
},
)
print(r2.summary())
# Variant 3 — variance-driven POD + custom NN learning rate
print("\n=== Variant 3: variance=0.99 POD + LR=5e-4 ===")
r3 = project.train(
model_name="tune_var99",
outputs={
"outlet_temperature": {
"pod": {"variance": 0.99},
"nn": {"learning_rate": 5e-4, "hidden_layers": [128, 64, 32]},
},
},
epochs=500,
)
print(r3.summary())
# --- Compare ---
print("\n=== Comparison ===")
print(f"{'variant':<20} {'modes':<6} {'test R²':<10} {'test RMSE':<12}")
print("-" * 50)
for r in [r1, r2, r3]:
for m in r.models:
modes = m.n_modes if m.n_modes is not None else "-"
print(f"{r.model_name:<20} {str(modes):<6} {m.test_metrics.r2:<10.4f} {m.test_metrics.rmse:<12.4g}")
discovering_bcs.py
Launches Fluent and lists every boundary condition, settable parameter path,
surface, and report definition exposed by the case. Useful when starting from
an unfamiliar case file. Also demonstrates the
Project.list_available_inputs()
shortcut.
"""Connect to Fluent and list everything cfdtwin can address as inputs/outputs.
Run this once for an unfamiliar case file — copy the bc names, parameter
paths, surface names, etc. into your set_inputs / set_outputs calls.
The last section shows the higher-level ``Project.list_available_inputs()``
helper, which gives you the same data already grouped by category and parsed
into a shape you can feed straight into ``set_inputs``.
"""
import tempfile
import ansys.fluent.core as pyfluent
from ansys.fluent.core import examples
import cfdtwin
CASE_FILE = examples.download_file("mixing_elbow.cas.h5", "pyfluent/mixing_elbow")
print(f"Loading {CASE_FILE}...\n")
solver = pyfluent.launch_fluent(
precision="single", processor_count=4, dimension=3, mode="solver",
)
solver.settings.file.read_case(file_name=CASE_FILE)
# --- Boundary conditions --------------------------------------------------
print("=== Boundary conditions ===")
bc_settings = solver.settings.setup.boundary_conditions
for bc_type in bc_settings.get_state():
bc_group = getattr(bc_settings, bc_type)
for bc_name in bc_group.get_state():
print(f" {bc_name:<24} type={bc_type}")
def walk_settable(obj, prefix="", depth=4):
"""Recursively find leaf settings that have a writable .value, yielding
(dotted-path, type-name) pairs. Used to show what set_inputs's rich-dict
`parameter_path` can target on a given BC.
Skips PyFluent nodes that raise InactiveObjectError (settings only
relevant under another flag — e.g. gauge_pressure is inactive when the
BC isn't pressure-driven).
"""
if depth <= 0:
return
try:
names = obj.child_names
except Exception:
return
for name in names:
if name in ("child_names", "command_names"):
continue
path = f"{prefix}.{name}" if prefix else name
try:
child = getattr(obj, name)
if hasattr(child, "value"):
yield path, type(child).__name__
continue
yield from walk_settable(child, prefix=path, depth=depth - 1)
except Exception:
# Inactive node, or settings tree disagrees with what's exposed
# at this point in the case. Either way, skip silently.
continue
# --- Settable parameter paths on each velocity-inlet ---------------------
print("\n=== Velocity-inlet settable parameters ===")
vel_inlets = solver.settings.setup.boundary_conditions.velocity_inlet
for bc_name in vel_inlets.get_state():
print(f" {bc_name}:")
inlet = vel_inlets[bc_name]
for path, type_name in walk_settable(inlet):
print(f" parameter_path={path!r:<48} ({type_name})")
# --- User-defined surfaces -----------------------------------------------
# results.surfaces is grouped by surface type (point_surface, zone_surface,
# plane_surface, ...) — iterate one level deeper to get the actual names.
print("\n=== Surfaces ===")
try:
surfaces = solver.settings.results.surfaces.get_state() or {}
any_found = False
for surf_type, items in surfaces.items():
if not items:
continue
for surf_name in items:
print(f" {surf_name:<24} type={surf_type}")
any_found = True
if not any_found:
print(" (no user-defined surfaces; BC names like 'outlet' still work as outputs)")
except Exception as e:
print(f" (unable to list: {e})")
# --- User-defined report definitions -------------------------------------
# Same shape as surfaces: top-level keys are report types, each holding a
# dict of user-created reports.
print("\n=== Report definitions ===")
try:
reports = solver.settings.solution.report_definitions.get_state() or {}
any_found = False
for report_type, items in reports.items():
if not items:
continue
for report_name in items:
print(f" {report_name:<24} type={report_type}")
any_found = True
if not any_found:
print(" (none defined in this case)")
except Exception as e:
print(f" (unable to list: {e})")
solver.exit()
# --- Same data via the cfdtwin API ----------------------------------------
# Project.list_available_inputs() wraps the raw PyFluent calls above. It
# returns one list with BCs and Fluent input parameters (named expressions
# flagged input_parameter=True) already merged, each entry tagged with a
# `category`. Feed entries straight back into set_inputs by attaching a range.
print("\n=== cfdtwin.Project.list_available_inputs() ===")
with tempfile.TemporaryDirectory() as tmp:
project = cfdtwin.Project.create(tmp, name="discovery_demo")
project.set_case_file(CASE_FILE)
project.connect_fluent(precision="single")
try:
for item in project.list_available_inputs():
cat = item['category']
if cat == 'Input Parameter':
print(f" [IP] {item['name']:<24} unit={item.get('unit','')} "
f"current={item.get('current_value')}")
else:
print(f" [BC] {item['name']:<24} type={item['type']}")
finally:
project.disconnect_fluent()