Skip to content

Full workflow tutorial

Walks the same pipeline as Quickstart but explains each step and shows what's on disk at each stage. Uses PyFluent's downloadable mixing_elbow case so you can reproduce this exactly.

Run it: docs/examples/full_workflow.py is the runnable version of this tutorial.

Get the case file

from ansys.fluent.core import examples
case_file = examples.download_file("mixing_elbow.cas.h5", "pyfluent/mixing_elbow")
print(case_file)

The download is cached under ~/.cache/ansys_fluent_core/examples/.

Create a project

import cfdtwin
project = cfdtwin.Project.create("./elbow_study", name="elbow_v1")
project.set_case_file(case_file)

elbow_study/project_info.json now exists with the case-file reference.

Declare inputs and outputs

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"]},
])

Input keys are "<bc_name>|<parameter_display>". The bc names (cold-inlet, hot-inlet) come from the Fluent case. See Discovering BC names from Fluent if you don't know them.

Categories must be one of:

  • Report Definition — scalar, e.g. an outlet temperature average
  • Surface — 2D field on a named surface
  • Cell Zone — 3D field across a cell zone

set_outputs writes output_parameters.json.

Generate DOE samples

project.generate_doe(n=20, method="lhs", seed=42)

Samples are written to doe_samples.json. LHS is recommended for any serious training run because it spreads samples evenly across the input space.

For a small grid sweep instead:

project.generate_doe(method="factorial", points_per_param=4)
# 4 ** 2 = 16 samples (two inputs, four levels each)

Run simulations

# mixing_elbow.cas.h5 is a single-precision case file. Fluent refuses to
# read a single-precision case in a double-precision session, so override.
project.connect_fluent(precision="single")
sim = project.run_simulations(iterations=100, verbose=True)
print(sim.summary())

Each DOE point is loaded into Fluent, the BCs are applied, the solver iterates, and outputs are extracted into dataset/sim_NNNN.npz. Coordinates for field outputs are saved once to dataset/coordinates.npz.

run_simulations() automatically skips already-complete sim IDs, so interrupting and restarting is safe.

sim is a SimulationResult with .successful, .failed, .failed_ids, .elapsed, .stopped_reason.

Train

result = project.train(model_name="elbow_v1", epochs=300)
print(result.summary())

This trains a POD+NN model per (location, field_variable) pair. For the case above, that's a single 2D field model on outlet predicting temperature.

result is a TrainingResult. result.models is a list of ModelInfo — one per trained sub-model.

Predict

pred = project.predict("elbow_v1", {
    "cold-inlet|momentum > velocity_magnitude": 0.4,
    "hot-inlet|momentum > velocity_magnitude":  0.8,
})
print(pred.values.shape)        # (1, n_points)
print(pred.coordinates.shape)   # (n_points, 3)

Field reconstructions need coordinates to be useful (for plotting, post-processing). cfdtwin loads them automatically from dataset/coordinates.npz.

For batch prediction, pass a list of dicts:

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]
]
pred = project.predict("elbow_v1", sweep)
print(pred.values.shape)        # (4, n_points)

What's on disk after all this

elbow_study/
├── project_info.json
├── model_setup.json
├── output_parameters.json
├── doe_samples.json
├── dataset/
│   ├── coordinates.npz
│   ├── sim_0001.npz
│   ├── ...
│   └── sim_0020.npz
└── models/
    └── elbow_v1/
        ├── outlet_temperature_nn.h5
        ├── outlet_temperature_nn.npz
        ├── outlet_temperature_pod.npz
        ├── outlet_temperature_metadata.json
        ├── outlet_temperature_loss_curve.png
        └── training_summary.json