Tutorials › Pharmacometric Workflows and Reproducible Modeling › Recording Model Version Lineage

Run Management and Model Comparison · Tutorial 176 of 399

Recording Model Version Lineage

Learn to build and validate a model-version lineage graph that preserves how each version was derived without confusing version history with run history.

Intermediate 11 min read

What You'll Learn

  • Define model versions as identifiable, immutable snapshots and represent their parent-child links as a directed graph.
  • Record the changes and referenced inputs that explain how each child version was derived.
  • Distinguish model-version lineage from run records and from a simple chronological list of files.
  • Check lineage graphs for missing parents, ambiguous edits, and cycles.
  • Choose between a single-parent history and a multi-parent graph when combining model changes.
  • Diagnose how incomplete or misleading lineage affects reproducibility and review.

Why Model-Version Lineage Matters

A model file changes as an analysis develops: a structural term may be added, a dataset reference may be updated, or a configuration may be revised. A filename and timestamp can show that two files differ, but not how one version was derived from another. Lineage records that relationship explicitly, allowing a reviewer to follow the sequence of changes from a starting point to a later model definition.

Lineage is useful when a model is simplified to investigate a failure, when a proposed structural change is evaluated, or when parallel edits are combined. It does not establish that a change is scientifically justified, nor does it say whether a run succeeded. It records ancestry: which version or versions a new version derives from, and what transformation was made.

Definition: A model version is an identifiable, preserved snapshot of the model definition at a particular state. A lineage graph is a directed graph whose nodes are model versions and whose edges indicate derivation: an edge from version \(v_i\) to version \(v_j\) means that \(v_j\) was derived from \(v_i\). A node may have one parent, several parents, or no parent if it is an explicitly designated starting version.

A version definition can include model structure, estimation-related configuration, and references to the data and supporting files that define the intended analysis. The exact boundary should be stated consistently for a project. A lineage record should not silently treat two different data inputs as the same just because the model code is identical. Conversely, a run is an execution of a model version under a particular execution setup; the version and its lineage are not themselves run outcomes.

Representing a Lineage Record

Use a stable identifier for each version and preserve the snapshot to which that identifier refers. A lineage record should identify its parent version or versions, the transformation that produced the child, and the components affected. It should also point to the relevant preserved artifacts or content identifiers, including data and configuration references when those are part of the model definition.

A concise record might contain the following fields:

  • Version identifier: unique within the project and never reused for a different snapshot.
  • Parent identifier or identifiers: explicit references to the version or versions from which this one was derived; use an explicit root designation for an initial version.
  • Change description: a brief, specific account of what changed and why, such as “added a lag time parameter” rather than “updated model.”
  • Changed components: for example, structural model, data reference, estimation configuration, or control-file settings.
  • Snapshot reference: an immutable artifact location or content hash that identifies the exact version contents.
  • Creation context: who made the version and when, where available, without using that information as a substitute for a stable identifier.

A hash can help detect whether preserved contents have changed: if the contents change, the hash will generally change as well. It is a useful integrity check, but it does not explain the scientific meaning of a change, prove that the record is complete, or replace an accessible snapshot. A human-readable change description and a preserved artifact serve different purposes.

Worked Example: Recording a Structural Change

Suppose a hypothetical analysis begins with version MV-020, a one-compartment model with first-order absorption. A modeler creates MV-021 by adding an absorption lag-time parameter, while leaving the data reference and other configuration unchanged.

The lineage edge is \(MV\text{-}020 \rightarrow MV\text{-}021\). A useful record for MV-021 names MV-020 as its parent, identifies the structural model as the changed component, and states that a lag-time parameter was added. It also points to the preserved MV-021 snapshot and retains the unchanged data and configuration references.

The record must not claim that MV-021 is a successful fit or a preferred model: those are not facts implied by derivation. Nor should it point to a moving file called “current_model” as the only snapshot reference, because its contents could later be overwritten. The lineage statement is precisely that MV-021 was derived from MV-020 by this specified structural change.

Graph Properties and a Justification

A useful lineage graph is a directed acyclic graph (DAG). “Directed” gives the edges a clear meaning: from parent to child. “Acyclic” means that following parent-to-child edges can never lead back to the version where the path began. A cycle would assert, for example, that MV-021 derives from MV-020 while MV-020 also derives from MV-021, which cannot describe a sequence of derivations.

Here is a simple justification for the acyclic condition. If every derivation creates a new immutable version after its parent version exists, assign each version a creation position in a valid derivation order. Along every edge, the child must come after the parent. Thus, the position strictly increases on each step of any directed path. A cycle would require the position to increase at every edge and return to its starting value, which is impossible. In practice, timestamps alone are not a sufficient ordering test: clock errors, imports, and retrospective record creation can make timestamps misleading. Check the graph relationships themselves.

Parent counts convey useful information. A root has no parent and marks a starting point. A single-parent child represents a direct derivation. A child with multiple parents represents a merge only when it actually combines material from each parent. Listing two versions as parents merely because they are related or were considered does not make the child a merge.

1
Assign the version identity.
Allocate a new, non-reused identifier for the snapshot being created. Preserve the prior version rather than overwriting it.
2
Declare ancestry.
Record the actual parent identifier or identifiers. Confirm that each referenced parent exists in the lineage record.
3
Describe the transformation.
Name the changed components and summarize what was changed. Include relevant data or configuration references when they differ.
4
Preserve and validate.
Store the child snapshot and check that identifiers are unique, parent references resolve, and no directed cycle exists.

Worked Example: Tracing a Data-Reference Change

Consider a hypothetical version MV-031 that uses a dataset labeled “extract A.” A later version, MV-032, is created after correcting a documented data-preparation issue; its model structure is unchanged, but it references a new, preserved dataset snapshot labeled “extract B.”

Record the edge \(MV\text{-}031 \rightarrow MV\text{-}032\). The change description should say that the data reference changed from the preserved extract A to the preserved extract B and briefly identify the reason for the correction. The record should retain the exact data snapshot identifiers so that “extract B” does not mean whichever file happens to have that name later.

Although the model code is identical, these are distinct model versions under a convention that includes data references in the version definition. The lineage is not a claim that the data correction improves the estimates. It simply makes the transition and its affected input traceable.

Single-Parent Histories, Branches, and Merges

A simple chain is often sufficient when each new version derives from exactly one previous version. But treating every history as a single chain can obscure parallel work. If two modelers independently derive changes from the same baseline, the honest representation is a branch: both children point to the shared parent. If a later version incorporates changes from both branches, it can have two parents, provided the merge is real and its resolution is documented.

A chronological list can be easy to read but loses this branching information. A graph preserves it directly. The alternative is to maintain separate histories and document a later version as derived from one branch only; that can be appropriate if the other branch was not incorporated. The choice should reflect actual derivation, not a preference for a tidier diagram.

Worked Example: Recording Parallel Edits and a Merge

In a hypothetical project, MV-040 is the shared baseline. One branch creates MV-041 by changing the residual-error specification. Separately, another branch creates MV-042 from MV-040 by revising the structural model. A later version MV-043 combines both changes.

The edges are \(MV\text{-}040 \rightarrow MV\text{-}041\), \(MV\text{-}040 \rightarrow MV\text{-}042\), \(MV\text{-}041 \rightarrow MV\text{-}043\), and \(MV\text{-}042 \rightarrow MV\text{-}043\). MV-043 has two parents because both versions contributed to it. Its change description should state how the residual-error and structural changes were brought together and identify any conflicts or choices made in doing so.

If MV-043 used the structural change from MV-042 but discarded the residual-error change from MV-041, it should not list MV-041 as a contributing parent. Its ancestry and description should instead show the actual source and transformation. Accurate parentage is more important than recording every version that was reviewed during development.

Diagnostics, Failure Modes, and Limits

Lineage is only as useful as the relationships and snapshots it records. Review a graph for missing parent identifiers, duplicate identifiers, child versions that point to themselves, cycles, and descriptions that do not match the changed artifacts. Also check that a version’s referenced data and configuration are preserved or resolvable, rather than relying on labels that can be reassigned.

  • Overwriting a parent: if its contents are changed after a child points to it, the edge no longer identifies the actual starting state. Preserve snapshots instead.
  • Vague change notes: “fixed model” does not tell a reviewer which component changed. Name the change and affected component.
  • False parent links: listing a version as a parent when none of its content was carried forward creates misleading ancestry. Record actual derivation, not discussion history.
  • Unrecorded data or configuration changes: identical model code can still represent a different analysis definition if its referenced inputs changed. Record those references explicitly.
  • Hash-only records: a hash can detect content differences but is not a readable change explanation and is not useful without access to the corresponding artifact.

Lineage records also have limits. They do not establish whether a model is scientifically sound, whether a dataset is appropriate, or whether a computation is reproducible in its execution environment. Nor do they replace run records that identify executions and their outcomes. The earlier material on organizing run directories concerns where run artifacts are kept; lineage adds the ancestry relationships between model versions and should link to, not substitute for, preserved artifacts.

Key takeaway: A reliable lineage graph links immutable, uniquely identified model versions with directed parent-to-child edges, specific change descriptions, and resolvable artifact references. Its structure should reflect actual derivation, including branches and genuine merges, rather than merely listing versions in date order.

Check Your Understanding

Answer each question, then compare your reasoning with the solutions.

  1. Version MV-051 was derived from MV-050 by changing a configuration setting. What minimum lineage information should the MV-051 record contain?
  2. Two versions were created independently from the same baseline. Should one be made the parent of the other solely because it has an earlier timestamp? Explain.
  3. A proposed graph contains the edges MV-060 to MV-061, MV-061 to MV-062, and MV-062 to MV-060. What is wrong with it?
  4. A new version uses unchanged model code but a different preserved data snapshot. Under a convention where data references are part of the version definition, should it receive a distinct version identity? Why?
  5. A child was created after reviewing two branches but incorporates changes from only one. How should its parent relationship be recorded?

Solutions. (1) Identify MV-050 as the parent, describe the configuration setting changed, point to the preserved MV-051 snapshot, and record the relevant configuration reference. (2) No. A parent edge means actual derivation, not earlier time; both versions should point to their shared baseline if both derive from it independently. (3) The edges form a directed cycle, so they cannot represent a valid derivation lineage; inspect and correct the parent records. (4) Yes. The changed input reference makes it a distinct version under the stated convention, and the record should identify both data snapshots. (5) Record only the version that actually contributed as the parent, and describe the incorporated change; do not list the unused branch as a parent.

Clinical Biostats