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.

For air-gapped CAE clusters, pre-download wheels with 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.

PatternTypical ExampleHow Python Talks to the Tool
Native Python APIPyMAPDL, Abaqus abaqus python, SalomeDirect in-process calls; solver runs inside the Python session or via a managed server
Script file + CLICalculiX, OpenFOAM, Code_AsterPython writes an input deck, invokes the solver binary, parses the output
File-based couplingParaView, EnSight, TecplotPython 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.

CAE Python Environment: Layers and Integration Paths Project Virtual Environment (.venv / conda env) Python 3.11 (pinned) requirements.txt (locked) ipykernel registered offline wheel cache Python Package Stack numpy < 2.0 pyvista / meshio gmsh / pythonocc pandas / h5py solver client Integration Patterns Native Python API PyMAPDL, Abaqus, Salome in-process / managed server Script + CLI CalculiX, OpenFOAM, Code_Aster subprocess + log parsing File-based Coupling ParaView, EnSight, Tecplot VTK / CGNS / HDF5 Automation Outputs parameter sweep results mesh quality reports convergence logs reproducible notebooks

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.

Never install CAE packages into the system Python or into a solver's bundled interpreter. Doing so can break the solver's own scripting interface, and the damage is often only discovered when a vendor tool refuses to start. Keep the project environment separate and activate it explicitly.

Reproducibility Checklist

  • Interpreter version pinned in environment.yml or documented in the README.
  • Dependencies split into requirements.in (intent) and requirements.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.