Semantics¶
NeoPKPD uses semantic versioning for numerical behavior to ensure reproducibility across versions.
Version Constants¶
Three independent version numbers track different aspects of system behavior:
NEOPKPD_VERSION = "0.1.0" # Software version
EVENT_SEMANTICS_VERSION = "1.0.0" # Dose event handling
SOLVER_SEMANTICS_VERSION = "1.0.0" # ODE solver behavior
ARTIFACT_SCHEMA_VERSION = "1.0.0" # JSON artifact format
Event Semantics (v1.0.0)¶
Event semantics define how dose events are normalized and applied.
Dose Normalization Rules¶
Given doses and simulation window [t0, t1]:
- Doses at t0: Added to initial state
u0 - Doses in (t0, t1]: Applied via ODE callbacks
- Doses outside [t0, t1]: Ignored
- Duplicate times: Summed together
# Example: doses at [0, 0, 12] with t0=0, t1=24
# - Doses at t=0 (100 + 50 = 150) → added to u0
# - Dose at t=12 → callback
doses = [
DoseEvent(0.0, 100.0),
DoseEvent(0.0, 50.0),
DoseEvent(12.0, 75.0)
]
Dose Target¶
Each model defines which compartment receives doses:
| Model | Dose Target |
|---|---|
OneCompIVBolus |
Central (index 1) |
OneCompOralFirstOrder |
Gut (index 1) |
Callback Timing¶
Doses in (t0, t1] are applied as preset time callbacks:
- Exact time precision (no interpolation)
- State discontinuity at dose time
- Solver restarts after callback
Solver Semantics (v1.0.0)¶
Solver semantics define ODE solving behavior and defaults.
Supported Algorithms¶
| Algorithm | Type | Use Case |
|---|---|---|
Tsit5 |
Explicit Runge-Kutta (5th order) | Non-stiff problems (default) |
Rosenbrock23 |
Rosenbrock method | Stiff problems |
Tolerance Interpretation¶
reltol: Relative error tolerance per stepabstol: Absolute error tolerance per step- Error estimate:
err ≤ abstol + reltol * |u|
Golden Standard Settings¶
For reproducibility, use:
Output Grid Alignment¶
Results are aligned exactly to saveat times:
- No interpolation artifacts
- Deterministic output regardless of internal step size
- Callback discontinuities preserved
State Continuity¶
For segmented simulations (IOV, time-varying covariates):
- End state of segment N → initial state of segment N+1
- No discontinuity except at dose times
Artifact Schema (v1.0.0)¶
Artifact schema defines the JSON structure for serialized simulations.
Common Fields¶
Every artifact contains:
{
"artifact_schema_version": "1.0.0",
"semantics_fingerprint": {
"artifact_schema_version": "1.0.0",
"event_semantics_version": "1.0.0",
"solver_semantics_version": "1.0.0"
}
}
Artifact Types¶
| Type | artifact_type Value |
|---|---|
| Single execution | "single" or absent |
| Population | "population" |
| Sensitivity (single) | "sensitivity_single" |
| Sensitivity (population) | "sensitivity_population" |
Numeric Precision¶
- All floating-point values stored with full precision
- No rounding or truncation
- JSON uses IEEE 754 double representation
Semantics Fingerprint¶
Every artifact includes a fingerprint for version tracking:
semantics_fingerprint() = Dict(
"artifact_schema_version" => ARTIFACT_SCHEMA_VERSION,
"event_semantics_version" => EVENT_SEMANTICS_VERSION,
"solver_semantics_version" => SOLVER_SEMANTICS_VERSION,
)
Fingerprint Verification¶
When replaying artifacts:
- Read stored fingerprint
- Compare to current versions
- Warn if mismatch (results may differ)
Version Bump Policy¶
When to Bump Event Semantics¶
- Change to dose normalization rules
- Change to dose application order
- Change to callback timing
- Change to dose target assignment
When to Bump Solver Semantics¶
- Change to default tolerances
- Change to algorithm selection logic
- Change to output grid alignment
- Change to segmentation behavior
When to Bump Artifact Schema¶
- Change to JSON field names
- Change to JSON structure
- Addition of required fields
- Removal of fields
Process for Bumping¶
- Identify Change: Document what behavior changes
- Bump Version: Increment appropriate version number
- Update Golden: Re-generate all affected golden artifacts
- Update Tests: Ensure tests pass with new behavior
- Document: Add entry to changelog
Compatibility¶
Forward Compatibility¶
Newer NeoPKPD versions can replay older artifacts:
- Missing fields use defaults
- Schema upgrades are automatic
Backward Compatibility¶
Older NeoPKPD versions may not replay newer artifacts:
- Unknown fields are ignored
- Missing required fields cause errors
Cross-Platform Compatibility¶
Same artifact should produce identical results on:
- Different operating systems
- Different Julia versions (≥1.9)
- Different hardware architectures
IIV/IOV Semantics¶
Random Number Generation¶
- Uses
Random.XoshiroPRNG - Seed fully determines sequence
- Separate seeds for IIV and IOV
Log-Normal Transform¶
Occasion Semantics (v1)¶
Occasion boundaries derived from dose times:
t0always starts occasion 1- Each unique dose time > t0 starts new occasion
- Times sorted and deduplicated
- Only times in [t0, t1] considered
Covariate Semantics¶
Application Order¶
Covariate effects applied in declaration order:
effects = [
CovariateEffect(PowerCovariate(), :CL, :WT, 0.75, 70.0),
CovariateEffect(LinearCovariate(), :CL, :AGE, -0.01, 40.0)
]
# Applied: CL → (WT effect) → (AGE effect)
Time-Varying Interpolation¶
Step: Value from rightmost knot ≤ t
series.times = [0, 24, 48]
series.values = [1, 2, 3]
value_at(15) = 1 # Uses knot at 0
value_at(24) = 2 # Uses knot at 24
Linear: Interpolate between knots
Sensitivity Semantics¶
Perturbation Application¶
Perturbations applied in declaration order:
plan.perturbations = [
Perturbation(RelativePerturbation(), :CL, 0.1),
Perturbation(AbsolutePerturbation(), :V, 5.0)
]
# Applied: CL → CL * 1.1, then V → V + 5
Metric Computation¶
All metrics computed element-wise on output series:
max_abs_delta = max(|base[i] - pert[i]|)
max_rel_delta = max(|base[i] - pert[i]| / |base[i]|)
l2_norm_delta = sqrt(sum((base[i] - pert[i])^2))
Examples¶
Verifying Semantics Match¶
using NeoPKPD
# Check current versions
println("Event semantics: ", EVENT_SEMANTICS_VERSION)
println("Solver semantics: ", SOLVER_SEMANTICS_VERSION)
println("Artifact schema: ", ARTIFACT_SCHEMA_VERSION)
# Check artifact fingerprint
artifact = read_execution_json("my_artifact.json")
fingerprint = artifact["semantics_fingerprint"]
if fingerprint["event_semantics_version"] != EVENT_SEMANTICS_VERSION
@warn "Event semantics version mismatch"
end