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.

LibraryPurpose in LS-DYNA automationTypical use
pandasTabular handling of node, element, and curve dataReading *NODE blocks into DataFrames, filtering, exporting
numpyVectorized coordinate and time-history mathTranslating node sets, resampling curves
lxmlRobust XML parsing for LS-PrePost command files and metadataReading .cfile and session XML
pyyamlExternalizing parameter sets and material cardsDriving parametric deck generation
pytestRegression testing of deck transformationsAsserting 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, or pip installed into the global interpreter.
  • UnicodeDecodeError — the deck contains non-UTF-8 bytes; add errors="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.
Run the smoke test after every dependency change. It takes under a second and catches environment drift before it corrupts a generated deck.
Keyword Deck Read/Write Pipeline decks/model.k source deck (read-only) read_deck() utf-8, errors=replace split_blocks() *KEYWORD boundaries transform edit target blocks write_deck() newline="\n" out/model_modified.k generated deck round-trip assert rebuilt == original mismatch → inspect block Failure symptoms and checks ModuleNotFoundError → venv not activated or pip installed globally UnicodeDecodeError → add errors="replace", log offending line numbers Round-trip mismatch → *-prefixed line inside comment or title card

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.