3. Version control fundamentals for models

Simulation models are not ordinary source files. A single model may consist of a block diagram, a parameter set, a solver configuration, a compiled binary, and a set of reference results — each with different sizes, lifecycles, and merge behaviors. Applying generic software version control practices without adaptation produces repositories that are either bloated with binaries or unable to reproduce a prior run. This section establishes the branching, tagging, merging, and repository-structure conventions needed to keep model artifacts traceable and reproducible.

What Version Control Must Capture for Models

Before choosing a workflow, classify the artifacts a model repository will hold. The classification drives nearly every later decision, because text-like artifacts merge cleanly while binary artifacts do not.

Artifact class Typical examples Merge behavior Storage strategy
Textual model source Modelica .mo, Simulink .m scripts, FMU build scripts, Python parameter files Line-level three-way merge Native Git tracking
Structured parameter data YAML/JSON calibration sets, CSV lookup tables Mergeable if key-ordered; CSV needs care Native Git, normalized formatting
Binary model containers Simulink .slx, compiled FMU .fmu, CAD geometry Not mergeable — last writer wins Git LFS or artifact store
Large result data MAT files, HDF5 logs, reference trajectories Not mergeable External artifact store, referenced by hash

The practical rule: keep mergeable text in the repository, push large binaries to Git LFS, and keep bulk result data outside the repository entirely, referenced by a content hash recorded in a manifest. This keeps clone times reasonable and prevents the history from becoming unrecoverable.

Branching Strategy for Model Development

Model development differs from application development in one important respect: a model change is only meaningful once it has been validated against a baseline. Branching should therefore be organized around validation boundaries rather than around individual developers.

A workable convention uses three long-lived branches and short-lived feature branches:

  • main — the integration branch. Every commit here must pass the regression suite against the approved baseline. This is the branch from which release tags are cut.
  • develop — the working integration branch where feature branches merge first. It may temporarily fail regression while a coordinated change is in progress.
  • release/<version> — a stabilization branch cut from develop when a candidate is frozen. Only defect fixes land here; new features do not.
  • feature/<ticket>-<slug> — short-lived branches for a single change, for example feature/MC-142-brake-thermal-gain.

Feature branches should be small and short-lived. A branch that changes both the plant model and the controller simultaneously is difficult to validate and nearly impossible to bisect when regression fails. Split such changes into separate branches that each pass regression independently.

Tagging for Reproducibility

A tag is the anchor that makes a simulation result reproducible. Every result that will be cited as evidence must be traceable to an immutable tag. Use annotated tags, not lightweight tags, because annotated tags carry a message, author, and date that survive repository migration.

# Freeze a validated model state
git tag -a model-v2.3.0 -m "Validated baseline: brake thermal model, solver CVODE, tol 1e-6"
git push origin model-v2.3.0

# Verify what a tag points to
git show model-v2.3.0 --stat

Adopt a semantic versioning scheme with model-specific meaning: major for changes that alter interface or expected output beyond tolerance, minor for additive changes that preserve baseline behavior, and patch for parameter or documentation corrections that do not change validated results. Record the tag name, the commit SHA, and the solver configuration in the run manifest so that a result can be regenerated exactly.

A tag alone does not guarantee reproducibility. The tag must be paired with the exact toolchain version, solver settings, and parameter set used. Store these in a run-manifest.yaml committed alongside the model, and reference the manifest hash in the result record.

Merging Model Artifacts

Textual model sources merge like code, but two failure modes are common. First, block-diagram tools often serialize models with unstable ordering, producing spurious diffs. Configure the tool to export a canonical, sorted representation before committing, or commit an exported text form alongside the binary. Second, parameter files merge cleanly at the line level but can produce semantically invalid combinations — for example, a gain from one branch combined with a limit from another. After any merge that touches parameters, run the parameter validation check before merging to main.

Binary containers cannot be merged. The correct procedure is to designate one branch as authoritative for the binary, merge the textual sources first, then regenerate the binary from the merged sources and commit it as a single new revision. Never attempt to resolve a binary conflict by choosing "ours" or "theirs" without regenerating, because the resulting binary will not correspond to any committed source state.

# Merge textual sources first
git checkout develop
git merge --no-ff feature/MC-142-brake-thermal-gain

# Regenerate binary from merged sources, then commit
sim-build --model brake_thermal --out build/brake_thermal.slx
git add build/brake_thermal.slx
git commit -m "Regenerate brake_thermal.slx from merged sources (MC-142)"

Repository Structure

A repository layout that separates mergeable sources from generated and external artifacts keeps history clean and makes the provenance chain explicit.

model-repo/
├── models/                  # textual model sources (mergeable)
│   ├── plant/
│   └── controller/
├── params/                  # YAML/JSON parameter sets (mergeable)
│   ├── baseline.yaml
│   └── calibration/
├── build/                   # generated binaries (Git LFS)
├── manifests/               # run-manifest.yaml per validated tag
├── tests/                   # regression cases and tolerances
└── .gitattributes           # LFS rules and diff drivers

The .gitattributes file is not optional. It tells Git which paths are binary, which use LFS, and which diff driver to apply. Without it, a single committed .mat file can permanently inflate the repository.

*.slx  filter=lfs diff=lfs merge=lfs -text
*.fmu  filter=lfs diff=lfs merge=lfs -text
*.mat  filter=lfs diff=lfs merge=lfs -text
*.yaml text eol=lf
*.mo   text eol=lf
Branch, Tag, and Artifact Flow for a Simulation Model Repository main develop feature/MC-142-brake-thermal-gain release/v2.3 model-v2.3.0 Repository (Git + LFS) models/ · params/ · build/ · manifests/ mergeable text + LFS binaries External Artifact Store result data (HDF5, MAT) by content hash referenced from run-manifest.yaml hash ref

Putting It Together: A Reproducible Run Record

The conventions above converge in a single artifact: the run manifest. When a simulation result is promoted to evidence, commit a manifest that binds the tag, commit SHA, toolchain, solver settings, parameter set, and result hash together. A minimal example:

tag: model-v2.3.0
commit: 9f3c1a7e2b
toolchain: Simulink R2023b / GCC 12.2
solver: CVODE, relTol 1e-6, absTol 1e-8
params: params/baseline.yaml@sha256:4d1e...
result: artifact-store://runs/2024-06-11-brake-thermal/result.h5
result_sha256: 7a2c...
regression: PASS (12/12 cases within tolerance)

With this record, any reviewer can check out the tag, apply the recorded parameters, and regenerate the result. If the regenerated hash differs, the provenance chain is broken and the evidence should be rejected until the discrepancy is explained. This is the practical payoff of disciplined branching, tagging, and repository structure: every simulation result becomes independently verifiable rather than merely asserted.

Do not tag a commit that has not passed regression against the approved baseline. A tag is a claim of validation; tagging an unvalidated state undermines the entire provenance chain and is difficult to retract once results have been cited against it.