2. Python environment for CAE automation
CAE automation rarely fails because the Python code is wrong. More often, it fails because the environment is wrong: a solver's Python API is bound to a specific interpreter version, a pre-processor ships its own bundled runtime, or a package upgrade silently breaks a scripting interface that worked last month. This chapter establishes a reproducible Python environment that can talk to solvers and pre/post-processors reliably, and that can be rebuilt on another workstation or in CI without guesswork.
Why the CAE Environment Is Different
General-purpose Python projects can usually rely on a single global interpreter plus a requirements file. CAE work is different in three ways. First, solver vendors pin their APIs to specific Python versions — for example, an ANSYS PyMAPDL client may require Python 3.10–3.11, while a Simcenter or Abaqus scripting interface may expect the interpreter bundled with the installation. Second, pre/post-processors such as ParaView expose Python through their own embedded interpreter, which is not the same as your system Python. Third, geometry and meshing libraries (pythonocc, gmsh, pyvista, meshio) carry heavy binary dependencies that are sensitive to OS, compiler, and BLAS/LAPACK builds.
The practical consequence is that you should treat the CAE Python environment as a first-class engineering artifact: versioned, documented, and reproducible. The rest of this chapter shows how to do that with virtual environments, package management, Jupyter, and explicit solver integration.
Virtual Environments: Isolation as a Design Decision
Create one virtual environment per project, not per machine. A project environment contains the exact interpreter version, the solver client libraries, and the geometry/mesh stack that the automation scripts depend on. Use venv for standard cases and conda/mamba when you need non-Python binary dependencies (OpenCASCADE, MPI, HDF5) that are painful to build from source.
# Standard library approach
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
python -m pip install --upgrade pip
# Conda approach for heavy binary stacks
conda create -n cae-auto python=3.11 -y
conda activate cae-auto
conda install -c conda-forge pyvista meshio gmsh pythonocc-core -y
Pin the interpreter version explicitly. If your solver client requires Python 3.11, do not create the environment with 3.12 and hope for the best — the failure mode is usually an ImportError on a compiled extension, or worse, a silent ABI mismatch that produces wrong numerical results.
Package Management: Pinning, Locking, and Vendored Wheels
Two files matter in every CAE automation project: a human-readable dependency specification and a machine-readable lock file. The specification states intent; the lock file guarantees reproducibility.
# requirements.in — intent
ansys-mapdl-core==0.68.*
pyvista==0.43.*
meshio==5.3.*
numpy<2.0
pandas==2.2.*
# Generate a lock file
pip install pip-tools
pip-compile requirements.in -o requirements.txt
pip-sync requirements.txt
Note the numpy<2.0 constraint. Many CAE libraries compiled against the NumPy 1.x C ABI will crash or return incorrect arrays under NumPy 2.x. This is a real, frequently encountered failure, not a hypothetical one. When a solver vendor ships a wheel, prefer that wheel over a source build; when only a source build exists, record the compiler and BLAS version in the project README.
pip download -r requirements.txt -d ./wheels and install offline with pip install --no-index --find-links=./wheels -r requirements.txt. This is standard practice on HPC systems without internet access.Jupyter for Interactive CAE Work
Jupyter is valuable in CAE automation for three specific tasks: exploring a solver's Python API before writing production scripts, inspecting mesh and result data interactively, and documenting a parameter study so that each run is reproducible. Register the project environment as a kernel so notebooks always use the correct interpreter:
pip install ipykernel
python -m ipykernel install --user --name cae-auto --display-name "Python (cae-auto)"
A typical exploration cell loads a result file and checks mesh quality before committing to a batch run:
import pyvista as pv
mesh = pv.read("bracket_v3.vtu")
print(mesh.n_points, mesh.n_cells)
print("volume:", mesh.volume)
print("bounds:", mesh.bounds)
mesh.plot(scalars="von_mises", cmap="turbo")
Keep notebooks for exploration and reporting; keep the actual automation logic in importable .py modules. A notebook that calls from cae_auto.mesh import check_quality is testable and reusable; a notebook that contains 400 lines of inline logic is not.
Integrating with Solvers and Pre/Post-Processors
Integration falls into three patterns, and choosing the right one determines how much of your workflow can be automated.
| Pattern | Typical Example | How Python Talks to the Tool |
|---|---|---|
| Native Python API | PyMAPDL, Abaqus abaqus python, Salome | Direct in-process calls; solver runs inside the Python session or via a managed server |
| Script file + CLI | CalculiX, OpenFOAM, Code_Aster | Python writes an input deck, invokes the solver binary, parses the output |
| File-based coupling | ParaView, EnSight, Tecplot | Python writes/reads neutral formats (VTK, CGNS, HDF5) and drives the tool via its scripting interface |
For the CLI pattern, wrap the solver call so that exit codes and log files are checked explicitly:
import subprocess, pathlib
def run_ccx(deck: pathlib.Path, workdir: pathlib.Path) -> int:
result = subprocess.run(
["ccx", "-i", deck.stem],
cwd=workdir, capture_output=True, text=True, timeout=3600,
)
(workdir / "solver.log").write_text(result.stdout + result.stderr)
if result.returncode != 0:
raise RuntimeError(f"CalculiX failed with code {result.returncode}")
return result.returncode
For the native API pattern, always verify the connection before running a study. A PyMAPDL session that appears to connect but is actually talking to a stale server will produce results from the wrong model — a failure that is easy to miss and expensive to debug.
Verifying the Environment Before a Study
Before launching a parameter sweep, run a short environment check. It should confirm the interpreter version, the solver connection, and the ability to write and read a neutral mesh file. A minimal check script looks like this:
import sys, importlib
required = ["numpy", "pyvista", "meshio", "ansys.mapdl.core"]
for name in required:
mod = importlib.import_module(name)
print(f"{name:20s} {getattr(mod, '__version__', 'n/a')}")
print("python:", sys.version)
# Solver connectivity check
from ansys.mapdl.core import launch_mapdl
mapdl = launch_mapdl()
print("MAPDL version:", mapdl.version)
mapdl.exit()
Typical failure symptoms and their causes: an ImportError on a compiled extension usually means an interpreter version mismatch; a DLL load failed on Windows points to a missing Visual C++ runtime or a conflicting NumPy build; a solver connection that hangs often indicates a firewall rule blocking the gRPC port or a stale server process. Recording these checks in the project README turns a two-hour debugging session into a two-minute diagnosis.
Reproducibility Checklist
- Interpreter version pinned in
environment.ymlor documented in the README. - Dependencies split into
requirements.in(intent) andrequirements.txt(locked). - NumPy major version constrained to match solver client ABI.
- Jupyter kernel registered against the project environment, not the base environment.
- Solver connection verified with a version query before any study runs.
- Offline wheel cache available for air-gapped or HPC deployment.
- Environment check script committed alongside the automation code.
With this foundation in place, the next step is to structure the automation code itself — separating geometry, meshing, solver invocation, and post-processing into importable modules that can be tested independently and reused across studies.