2. Include Path Resolution Audit

Why INCLUDE resolution deserves its own audit

In a full-vehicle crash model deck, an INCLUDE statement is a promise: at assembly time, the solver will open the referenced file and splice its contents into the deck. When that promise fails, the failure is rarely graceful. LS-DYNA may abort with a terse *INCLUDE error, or — worse — silently resolve to a stale copy of a subsystem sitting in a different directory, producing a model that runs to completion but no longer matches the release manifest. The Include Path Resolution Audit exists to catch both classes of problem before the deck is assembled and handed to the solver.

The audit has three concrete outputs: a resolved absolute path for every INCLUDE, a classification of each reference as resolved, missing, or ambiguous, and a short list of remediation actions. It is a static analysis task — no solver run is required — which makes it cheap to repeat on every build.

The resolution model you must audit against

Path resolution depends on the working directory from which the solver is launched, not on the location of the deck file. This is the single most common source of confusion. If the deck is launched as lsdyna i=deck/main.key from /build/release_42/, then a relative include such as *INCLUDE with subsystems/body/body_v17.key resolves to /build/release_42/subsystems/body/body_v17.key — not to a path relative to deck/. Absolute includes bypass this rule entirely and resolve wherever they point, which is convenient on the author's workstation and dangerous on a build server.

Two further rules matter for the audit:

  • Nested includes. A subsystem file may itself contain INCLUDE statements. Those are resolved relative to the same launch directory, not relative to the parent include. The audit must therefore recurse.
  • Case sensitivity and separators. Linux build servers are case-sensitive; a reference that works on Windows (Body_V17.key) may fail on the cluster (body_v17.key). Mixed \ and / separators are a related hazard.
Do not "fix" a missing include by copying a file into the launch directory. That masks the defect and guarantees the next clean build will fail again. Record the intended path and correct the reference or the directory layout.

Audit procedure

The audit is best performed with a small script that walks the deck, resolves each reference against the declared launch directory, and reports the outcome. The following Python example is self-contained and uses only the standard library. It assumes a launch directory and a deck entry file, and it recurses into nested includes.

import os, re, sys

LAUNCH_DIR = "/build/release_42"          # directory the solver is launched from
ENTRY      = "deck/main.key"              # path to the top-level deck, relative to LAUNCH_DIR

INCLUDE_RE = re.compile(r'^\s*\*INCLUDE\s*,?\s*(.+?)\s*$', re.IGNORECASE)

def resolve(ref, launch_dir):
    """Return (absolute_path, kind) where kind is 'absolute' or 'relative'."""
    ref = ref.strip().strip('"').strip("'")
    if os.path.isabs(ref):
        return os.path.normpath(ref), "absolute"
    return os.path.normpath(os.path.join(launch_dir, ref)), "relative"

def audit(entry, launch_dir):
    findings = []
    seen = set()
    stack = [os.path.join(launch_dir, entry)]
    while stack:
        current = stack.pop()
        if current in seen:
            continue
        seen.add(current)
        if not os.path.isfile(current):
            findings.append((current, "MISSING", "parent file not found"))
            continue
        with open(current, "r", errors="replace") as fh:
            for lineno, line in enumerate(fh, 1):
                m = INCLUDE_RE.match(line)
                if not m:
                    continue
                ref = m.group(1).split(",")[0].strip()
                abs_path, kind = resolve(ref, launch_dir)
                if not os.path.isfile(abs_path):
                    status = "MISSING"
                elif kind == "absolute":
                    status = "AMBIGUOUS"   # absolute paths are not portable
                else:
                    status = "RESOLVED"
                findings.append((abs_path, status, f"{current}:{lineno} ({kind})"))
                if status != "MISSING":
                    stack.append(abs_path)
    return findings

if __name__ == "__main__":
    for path, status, origin in audit(ENTRY, LAUNCH_DIR):
        print(f"{status:9s} {path}   <- {origin}")

Run it from any directory; the script anchors everything to LAUNCH_DIR, which is the value you must confirm with the build engineer. A representative output for a deck with three subsystems might look like this (illustrative example, not a real run):

RESOLVED  /build/release_42/subsystems/body/body_v17.key        <- deck/main.key:12 (relative)
RESOLVED  /build/release_42/subsystems/chassis/chassis_v09.key  <- deck/main.key:13 (relative)
MISSING   /build/release_42/subsystems/restraints/belt_v04.key  <- deck/main.key:14 (relative)
AMBIGUOUS /home/author/models/barrier/barrier_v02.key           <- deck/main.key:15 (absolute)

Each line tells you exactly what to do. The MISSING entry means the file is absent at the resolved location — check whether the subsystem was exported under a different name or whether the reference has a typo. The AMBIGUOUS entry is a portability defect: the absolute path works on the author's machine but will not exist on the build server, so it must be rewritten as a relative reference under subsystems/.

Reading the audit results

The SVG below shows how a single deck fans out into resolved, missing, and ambiguous references, and how each class maps to a remediation action. Use it as a mental model when triaging the script output.

Launch directory /build/release_42 deck/main.key 4 INCLUDE statements launched from RESOLVED subsystems/body/body_v17.key RESOLVED subsystems/chassis/chassis_v09.key MISSING subsystems/restraints/belt_v04.key AMBIGUOUS /home/author/models/barrier_v02.key No action record in audit log No action record in audit log Fix reference or export re-run audit Rewrite as relative path under subsystems/ Gate: assembly proceeds only when MISSING = 0 and AMBIGUOUS = 0

Handling the two defect classes

Missing references are usually a naming or export problem. Confirm the subsystem was exported with the expected filename and revision suffix, then either correct the INCLUDE line or re-export the file. If the file exists but under a different case (Belt_V04.key vs belt_v04.key), the fix belongs in the reference, not in a rename on the build server — renaming breaks other consumers.

Ambiguous references are absolute paths or paths that resolve to more than one candidate. Absolute paths must be rewritten relative to the launch directory. A subtler ambiguity arises when the same filename exists in two locations on the search path; the audit script above does not model a search path, so if your build uses one, extend resolve() to enumerate candidates and flag any reference with more than one match. Treat any such flag as a blocker.

Run the audit twice: once against the developer's working copy and once against a clean checkout of the release tag. A reference that resolves in the working copy but fails in the clean checkout is exactly the defect this audit is designed to surface.

Audit record to attach to the build

Keep a short, machine-readable record alongside the deck so the assembly step can verify the gate. The following table is the minimum useful set of fields; fill one row per INCLUDE.

Include (as written)Resolved absolute pathKindStatusAction
subsystems/body/body_v17.key/build/release_42/subsystems/body/body_v17.keyrelativeRESOLVEDnone
subsystems/chassis/chassis_v09.key/build/release_42/subsystems/chassis/chassis_v09.keyrelativeRESOLVEDnone
subsystems/restraints/belt_v04.key/build/release_42/subsystems/restraints/belt_v04.keyrelativeMISSINGre-export belt subsystem
/home/author/models/barrier/barrier_v02.key/home/author/models/barrier/barrier_v02.keyabsoluteAMBIGUOUSrewrite as subsystems/barrier/barrier_v02.key

The gate is simple and should be enforced by the assembly script: proceed only when every row is RESOLVED. Any MISSING or AMBIGUOUS row blocks assembly and is returned to the subsystem owner with the resolved path and the originating deck line number, which is enough for them to reproduce the failure without further context.