3. Python environment setup for Six Sigma and LS-DYNA automation

A Six Sigma analytics workflow that drives LS-DYNA batch studies sits at the intersection of two very different software worlds. On one side are statistical libraries that expect tidy numeric arrays and DataFrame objects; on the other are solver executables that read keyword decks, write binary and ASCII result files, and report success or failure through exit codes and log text. The Python environment is the bridge between them. Getting that environment right — pinned, reproducible, and verified — prevents the most common class of failures in this course: analyses that run but silently operate on stale or malformed data, and batch jobs that appear to launch but never produce usable results.

Choosing and isolating the interpreter

Use a 64-bit CPython interpreter, version 3.10 or 3.11 for this course. Both are widely supported by the scientific stack and by the LS-DYNA post-processing libraries used later. Create a dedicated virtual environment per project rather than installing into the system interpreter. With venv:

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

If your team already standardizes on Conda, the equivalent is conda create -n sixsigma-lsdyna python=3.11. Either tool is acceptable; the requirement is isolation. A dedicated environment guarantees that a NumPy upgrade performed for one study cannot break a validated analysis script from another, and it makes the environment exportable as a lockfile for audit purposes — a genuine Six Sigma concern, since measurement system reproducibility includes the software measurement system.

The core scientific stack and what each library contributes

Install the stack in one resolved transaction so that pip selects mutually compatible versions:

pip install numpy pandas scipy statsmodels matplotlib

Each library has a distinct role in the workflow, and understanding those roles prevents misuse:

  • NumPy provides the ndarray and vectorized arithmetic. It is the substrate for every DOE matrix, response vector, and Monte Carlo sample set. Keep array shapes explicit: a DOE with n runs and k factors is an (n, k) float array, and a response vector is (n,).
  • pandas holds the experimental record: one row per run, columns for factor settings, response values, run order, and block identifiers. Its labeled indexing and group-by operations make run-order and block effects easy to inspect.
  • SciPy supplies distributions, hypothesis tests, and optimization routines — for example scipy.stats.f_oneway for a quick ANOVA check or scipy.optimize.minimize for response-surface optimization.
  • statsmodels provides the regression and ANOVA machinery with full model summaries: ols with formula syntax, anova_lm for sequential sums of squares, and diagnostic statistics such as condition number and Durbin–Watson.
  • matplotlib produces the residual plots, main-effects plots, and contour plots that make a study reviewable. Set a non-interactive backend (matplotlib.use("Agg")) in batch scripts so figures render without a display.

Pin the resolved versions immediately after installation. pip freeze > requirements.txt captures the exact set; commit that file alongside the analysis scripts. When a study is later re-run for verification, the same lockfile reproduces the same numerical behavior.

Controlling LS-DYNA through subprocess

LS-DYNA is invoked as an external process. The subprocess module is the correct tool, and the design decisions that matter are argument passing, working directory, output capture, and timeout handling. A robust launcher looks like this:

import subprocess, pathlib, shlex

def run_lsdyna(deck: pathlib.Path, workdir: pathlib.Path,
               solver: str = "ls-dyna_smp_d_R11_1_winx64_ifort131.exe",
               ncpu: int = 4, timeout_s: int = 7200) -> dict:
    cmd = [solver, f"i={deck.name}", f"ncpu={ncpu}", "memory=200m"]
    try:
        proc = subprocess.run(
            cmd, cwd=workdir, capture_output=True, text=True,
            timeout=timeout_s, check=False)
    except subprocess.TimeoutExpired:
        return {"status": "timeout", "returncode": None, "log": ""}
    return {"status": "ok" if proc.returncode == 0 else "error",
            "returncode": proc.returncode,
            "log": proc.stdout + proc.stderr}

Several details in this launcher are deliberate. The command is a list, not a shell string, so paths containing spaces need no quoting and shell injection is impossible. cwd=workdir ensures the solver writes d3plot, glstat, messag, and d3hsp into the run directory rather than wherever the Python process happened to start. capture_output=True with text=True returns the solver's console output as a string for logging. check=False lets the caller inspect the return code instead of raising, which matters in batch loops where one failed run should not abort the remaining study. The timeout guards against a diverging explicit simulation consuming a compute node indefinitely.

A zero return code from LS-DYNA does not by itself prove the run converged. The solver can terminate normally after detecting negative volumes or excessive energy error. Always parse the termination status from messag or d3hsp and check the energy ratio in glstat before accepting a result into the study dataset.

How the pieces connect in a batch study

The figure below shows the data path through one complete study iteration: the DOE table in pandas generates per-run keyword decks, the subprocess launcher executes each deck in its own directory, and the extracted responses return to pandas for statsmodels analysis and matplotlib reporting.

DOE Table (pandas) run, t_plate, rib_h, fillet_r, material DataFrame (n × k) run order preserved Deck Generator template + parameter substitution run_001/main.k … run_00n/main.k LS-DYNA Solver subprocess.run( cwd=run_dir, timeout) d3plot, glstat, messag, d3hsp Response Parser peak force, max intrusion, energy status check first then extract values responses merged back into DataFrame (one row per run) statsmodels ols + anova_lm effects, residuals, lack-of-fit matplotlib main effects, contour, residual Agg backend fit model

Verifying the environment before trusting it

Run a short smoke test after installation. It should confirm imports, versions, and — critically — that subprocess control works end to end. A minimal check that exercises the whole chain without a real solver is to launch a trivial command and confirm captured output:

import sys, subprocess, numpy, pandas, scipy, statsmodels, matplotlib
print(sys.version)
for m in (numpy, pandas, scipy, statsmodels, matplotlib):
    print(m.__name__, m.__version__)
r = subprocess.run([sys.executable, "-c", "print('solver-proxy-ok')"],
                   capture_output=True, text=True, timeout=30)
assert r.returncode == 0 and "solver-proxy-ok" in r.stdout
print("subprocess control verified")

Then substitute the real solver executable with a one-line input deck and confirm that the expected output files appear in the working directory. If the solver is not on PATH, pass its absolute path; if it launches but produces no files, check cwd first, then the captured log for license or keyword errors. These two checks — import integrity and subprocess round-trip — catch nearly every environment defect before it contaminates a study.

Record the interpreter path, package versions, solver build string, and platform in a short environment.md stored with each study. When a response value later looks anomalous, this record lets you distinguish a genuine process effect from a software change between runs.

With a pinned, isolated environment and a verified launcher, the remaining chapters can treat the solver as a reliable function: given a deck, it returns a status and a set of result files. That abstraction is what makes automated DOE execution, response extraction, and statistical modeling practical at study scale.