3. Python environment setup for LS-DYNA automation

Before a single DOE case can be launched, the automation environment must be deterministic. LS-DYNA automation fails in subtle ways when the Python interpreter, the solver executable, and the working directory are not pinned down: a case may run with the wrong solver version, write results into an unexpected folder, or silently reuse a stale d3plot from a previous run. This chapter establishes a reproducible environment layout, the minimal library set, and a thin execution wrapper that every later script in the course will reuse.

Interpreter and dependency baseline

Use a dedicated virtual environment per project rather than the system interpreter. The reason is practical: LS-DYNA automation scripts depend on a small, stable set of packages, and isolating them prevents version drift when other projects upgrade numpy or pandas. A Python 3.10+ interpreter is recommended because the type-hint syntax and pathlib behavior used in later chapters are stable there.

python -m venv .venv
# Windows
.venv\Scripts\activate
# Linux / macOS
source .venv/bin/activate

pip install numpy pandas matplotlib pyyaml lxml

The library roles are distinct and worth stating explicitly, because choosing the wrong one for a task creates fragile code:

  • numpy — numeric arrays for parameter sweeps and for post-processing result vectors (e.g., acceleration time histories).
  • pandas — tabular DOE matrices, run-status logs, and injury-metric summary tables; its CSV and Excel I/O is the backbone of reporting.
  • matplotlib — quick diagnostic plots of response curves before formal report generation.
  • pyyaml — human-editable configuration files for solver paths, case templates, and DOE definitions.
  • lxml — parsing LS-DYNA keyword files and XML-based result exports when regex is too brittle.

Note what is deliberately absent: no LS-DYNA Python API is assumed. LS-DYNA is driven as an external process through its command-line executable, and Python's role is orchestration, file manipulation, and post-processing. This keeps the automation portable across solver versions and operating systems.

Directory conventions

A fixed directory contract is the single most effective defense against "which run produced this file?" confusion. The layout below separates immutable inputs from generated outputs, so a run can be deleted and regenerated without touching source data.

project/
├── config/
│   ├── environment.yaml      # solver path, license settings, thread count
│   └── doe_matrix.csv        # parameter combinations
├── templates/
│   └── base_model.k           # keyword template with placeholders
├── cases/
│   └── case_0001/            # one directory per DOE run
│       ├── model.k
│       ├── run.log
│       └── results/
├── scripts/
│   ├── env_setup.py
│   ├── runner.py
│   └── postprocess.py
└── reports/
    └── summary.xlsx

Two rules make this layout work. First, every case directory is self-contained: the generated model.k lives beside its outputs, so archiving or deleting a case is a single folder operation. Second, case identifiers are zero-padded (case_0001, not case_1) so lexical sorting matches numeric order in logs and reports. The environment.yaml file centralizes machine-specific paths, which means the same scripts run on a workstation and a cluster node without edits:

solver:
  executable: "C:/LSDYNA/lsdyna.exe"   # or /opt/ls-dyna/lsdyna
  ncpu: 4
  memory_words: 500000000
paths:
  project_root: "."
  template: "templates/base_model.k"

The execution wrapper

Calling the solver directly from every script duplicates error handling and makes logging inconsistent. A single wrapper function solves this. It builds the command line, redirects stdout/stderr to run.log, waits for completion, and returns a structured status rather than a bare exit code.

import subprocess, pathlib, datetime

def run_lsdyna(case_dir: pathlib.Path, exe: str, ncpu: int = 4) -> dict:
    model = case_dir / "model.k"
    log = case_dir / "run.log"
    cmd = [exe, f"i={model.name}", f"ncpu={ncpu}"]
    start = datetime.datetime.now()
    with open(log, "w") as fh:
        proc = subprocess.run(cmd, cwd=case_dir, stdout=fh,
                              stderr=subprocess.STDOUT)
    return {
        "case": case_dir.name,
        "returncode": proc.returncode,
        "elapsed_s": (datetime.datetime.now() - start).total_seconds(),
        "log": str(log),
    }

The cwd=case_dir argument is essential: LS-DYNA writes d3plot, glstat, and messag files into the current working directory, so running from the case folder guarantees outputs land in the right place. The wrapper returns a dictionary that the DOE driver can append directly to a run-status table.

A return code of 0 does not guarantee a physically valid run. LS-DYNA can terminate "successfully" after an energy error or negative-volume warning. Always scan messag and glstat for error keywords before trusting results — this check is added in the post-processing chapter.

Environment verification

Before launching a DOE, run a smoke test that confirms the solver is reachable and the wrapper behaves. A minimal single-element model or a short existing case is sufficient. The expected observable outcome is a run.log containing the LS-DYNA version banner and a normal termination message, plus a non-empty d3plot in the case directory. Typical failure symptoms and their causes:

SymptomLikely causeFix
FileNotFoundError on executableWrong path in environment.yamlUse absolute path; verify with Path(exe).exists()
Log empty, return code non-zeroLicense checkout failureCheck license server env vars; test solver manually
Outputs in project root, not case dircwd not setPass cwd=case_dir to subprocess.run
Stale results reusedOld d3plot presentClear results/ before each run

The diagram below summarizes how configuration, the wrapper, and the case directory interact during a single run.

Execution Wrapper Data Flow environment.yaml solver.exe ncpu, memory template path doe_matrix.csv case_0001 … N parameter rows run_lsdyna() build cmd cwd = case_dir redirect → run.log return status dict cases/case_0001/ model.k run.log d3plot, glstat results/ LS-DYNA solver external process writes outputs

With this environment in place, every subsequent chapter can assume three things: a known interpreter with pinned libraries, a case directory that owns its own inputs and outputs, and a wrapper that returns a uniform status record. Those three assumptions are what make a 50-case DOE reproducible rather than a collection of manual runs.