2. Python environment setup for LS-DYNA automation
LS-DYNA keyword files are plain-text decks organized as *KEYWORD blocks with fixed-width or comma-delimited fields. Automating their generation and editing with Python is therefore less about exotic APIs and more about disciplined text handling, reliable parsing, and a reproducible runtime. This chapter establishes that runtime and a minimal read/write workflow you will reuse in every later chapter.
Choosing and Installing a Python Interpreter
Use CPython 3.10 or 3.11 (64-bit). These versions are mature, well supported by the scientific stack, and avoid the deprecation churn of 3.12+ for some binary wheels. On Windows, install from python.org and check Add python.exe to PATH; on Linux, prefer a distribution package or pyenv rather than the system interpreter, which is often reserved for OS tooling.
# Verify the interpreter and platform
python --version
python -c "import sys, platform; print(sys.executable); print(platform.architecture())"
Expected output is a path to a 64-bit interpreter. If platform.architecture() reports 32-bit, reinstall — LS-DYNA decks routinely exceed the memory ceiling of 32-bit processes when parsed into memory.
Isolating Dependencies with a Virtual Environment
Never install automation libraries into the global interpreter. Create one virtual environment per project so that a deck-processing tool for one solver version does not collide with another.
python -m venv .venv
# Windows
.venv\Scripts\activate
# Linux / macOS
source .venv/bin/activate
python -m pip install --upgrade pip
Once activated, python and pip resolve inside .venv. Confirm with python -c "import sys; print(sys.prefix)"; the printed prefix must end in .venv.
Required Libraries and Their Roles
The core stack is deliberately small. Each library earns its place by solving a specific problem in keyword processing.
| Library | Purpose in LS-DYNA automation | Typical use |
|---|---|---|
pandas | Tabular handling of node, element, and curve data | Reading *NODE blocks into DataFrames, filtering, exporting |
numpy | Vectorized coordinate and time-history math | Translating node sets, resampling curves |
lxml | Robust XML parsing for LS-PrePost command files and metadata | Reading .cfile and session XML |
pyyaml | Externalizing parameter sets and material cards | Driving parametric deck generation |
pytest | Regression testing of deck transformations | Asserting round-trip fidelity |
pip install pandas numpy lxml pyyaml pytest
Pin versions in a requirements.txt once the stack works, so a colleague or CI runner reproduces the exact environment:
pip freeze > requirements.txt
pip install -r requirements.txt
A Minimal Read/Write Workflow for Keyword Files
Keyword decks are line-oriented. The safest automation pattern is: read the file as text, split it into *KEYWORD blocks, transform only the blocks you care about, and write everything back in original order. This preserves comments, blank lines, and cards your script does not understand — a property that full parsers often lose.
from pathlib import Path
def split_blocks(text: str):
"""Yield (keyword, lines) tuples, preserving order and comments."""
blocks, current, name = [], [], None
for line in text.splitlines(keepends=True):
if line.lstrip().startswith("*") and not line.lstrip().startswith("*$"):
if name is not None:
blocks.append((name, current))
name, current = line.strip(), [line]
else:
current.append(line)
if name is not None:
blocks.append((name, current))
return blocks
def read_deck(path: Path) -> str:
return path.read_text(encoding="utf-8", errors="replace")
def write_deck(path: Path, text: str) -> None:
path.write_text(text, encoding="utf-8", newline="\n")
Two details matter here. First, errors="replace" prevents a stray non-UTF-8 byte in a legacy deck from aborting the run; you can log the replacement characters and inspect them later. Second, forcing newline="\n" on write keeps decks portable between Windows and Linux solver runs, since LS-DYNA accepts LF line endings on all platforms.
A round-trip check is the fastest way to prove your reader and writer are lossless:
src = Path("model.k")
original = read_deck(src)
blocks = split_blocks(original)
rebuilt = "".join("".join(lines) for _, lines in blocks)
assert rebuilt == original, "Round-trip mismatch"
print(f"{len(blocks)} keyword blocks, {len(original)} chars")
If the assertion fires, the usual cause is a line that begins with * inside a comment or a title card. Inspect the offending block by printing its first three lines and comparing against the source.
Establishing the Working Directory Layout
Keep decks, scripts, and outputs separate so that a script never overwrites its own input. A layout that scales to the later chapters looks like this:
project/
decks/ # source .k files (read-only)
scripts/ # Python automation
params/ # YAML parameter sets
out/ # generated decks and logs
tests/ # pytest regression cases
Scripts should resolve paths relative to the project root, not the current working directory, so they behave identically when invoked from a scheduler or IDE:
ROOT = Path(__file__).resolve().parents[1]
deck = ROOT / "decks" / "model.k"
out = ROOT / "out" / "model_modified.k"
Verifying the Environment End to End
Before writing any transformation logic, run a smoke test that exercises the full chain: read a deck, split it, count blocks, write it to out/, and re-read the output. A passing run prints a block count and a byte-identical confirmation. A failing run typically shows one of three symptoms:
ModuleNotFoundError— the virtual environment is not activated, orpipinstalled into the global interpreter.UnicodeDecodeError— the deck contains non-UTF-8 bytes; adderrors="replace"and log the line numbers.- Round-trip assertion failure — a
*-prefixed line inside a comment or title card is being misclassified as a keyword header.
What You Should Have at the End of This Chapter
A working virtual environment with pinned dependencies, a project directory that separates inputs from outputs, and a verified read/split/write pipeline that round-trips a real deck without modification. That pipeline is the substrate for every subsequent chapter: parametric generation, batch editing, and validation all operate on the block structure you established here. Keep the smoke test in tests/ and run it whenever the environment changes.