Simple Example: Linear Two-State System
This example walks through a complete NullStrike analysis using the simplest possible model - a linear two-state system. This tutorial is perfect for understanding the core concepts without complex mathematics.
The Model
We'll analyze a simple linear system representing, for example, drug distribution between two compartments:
Where:
- \(x_1\): Amount in compartment 1
- \(x_2\): Amount in compartment 2
- \(k_1\): Transfer rate from compartment 1 to 2
- \(k_2\): Transfer rate from compartment 2 to 1
- \(y\): Observable output (only compartment 1 is measured)
Model Definition
Create the file custom_models/simple_linear.py
:
import sympy as sym
# ============================================================================
# MODEL: Simple Linear Two-Compartment System
# Description: Basic linear system for demonstrating NullStrike analysis
# ============================================================================
# State variables
x1 = sym.Symbol('x1') # Amount in compartment 1
x2 = sym.Symbol('x2') # Amount in compartment 2
# Parameter variables
k1 = sym.Symbol('k1') # Transfer rate 1->2
k2 = sym.Symbol('k2') # Transfer rate 2->1
# ============================================================================
# SYSTEM DEFINITION
# ============================================================================
# State vector
x = [[x1], [x2]]
# Parameter vector
p = [[k1], [k2]]
# Output vector (only x1 is observable)
h = [x1]
# No inputs
u = []
# No unknown inputs
w = []
# System dynamics
f = [
[-k1*x1 + k2*x2], # dx1/dt
[k1*x1 - k2*x2] # dx2/dt
]
# Required boilerplate
variables_locales = locals().copy()
Configuration File
Create custom_options/options_simple_linear.py
:
# ============================================================================
# CONFIGURATION: Simple Linear System
# ============================================================================
import sympy as sym
from math import inf
# Model identification
modelname = 'simple_linear'
# Analysis options
checkObser = 1 # Check state observability
maxLietime = inf # No time limit (simple model)
nnzDerU = [0] # No input derivatives
nnzDerW = [inf] # Unknown input derivatives
prev_ident_pars = [] # No pre-identified parameters
# Visualization configuration
MANIFOLD_PLOTTING = {
"var_ranges": {
"k1": (0.1, 2.0, 50), # Transfer rate 1->2
"k2": (0.1, 2.0, 50), # Transfer rate 2->1
},
"positive_symbols": ["k1", "k2"],
"default_positive_range": (0.1, 5.0, 100),
"log_for_positive": False, # Linear scale for simplicity
"default_var_range": (-2.0, 2.0, 100),
# Plot specific parameter combinations
"extra_pairs_2d": [
("k1", "k2"),
],
# Limit plots for this simple example
"max_triplets_3d": 0, # No 3D plots
"max_pairs_2d": 5, # Few 2D plots
}
Running the Analysis
Step 1: Quick Test
First, test that everything is set up correctly:
Expected output:
=== NULLSTRIKE ANALYSIS: simple_linear ===
Loading model: simple_linear
Loading options: options_simple_linear
=== STRIKE-GOLDD Analysis ===
Computing Lie derivatives... Done
Building observability matrix... Done
Rank analysis... Done
Found 2 identifiable parameters out of 2
=== Nullspace Analysis ===
Computing nullspace basis... Done
Nullspace dimension: 0
All parameters are identifiable!
Analysis complete!
Step 2: Full Analysis
Now run the complete analysis with visualizations:
This generates all plots and detailed reports.
Understanding the Results
Mathematical Analysis
For this linear system, the observability matrix is:
The Jacobian with respect to parameters is:
Key insight: The rank of \(\mathcal{J}\) depends on the state values \(x_1\) and \(x_2\).
Identifiability Results
The analysis reveals:
- Both parameters are identifiable when \(x_1 \neq 0\) and \(x_2 \neq 0\)
- The nullspace is empty (dimension 0)
- No parameter combinations are needed - individuals are identifiable
Physical Interpretation
This result makes intuitive sense:
- \(k_1\) identifiable: The rate \(x_1\) decreases tells us about \(k_1\)
- \(k_2\) identifiable: The contribution to \(\frac{dx_1}{dt}\) from \(x_2\) tells us about \(k_2\)
- Both needed: We need non-zero amounts in both compartments to see both rates
Examining the Output Files
Analysis Report
Check results/simple_linear/analysis_report.txt
:
STRUCTURAL IDENTIFIABILITY ANALYSIS: simple_linear
================================================
MODEL SUMMARY:
- States: 2 (x1, x2)
- Parameters: 2 (k1, k2)
- Outputs: 1 (x1)
IDENTIFIABILITY RESULTS:
IDENTIFIABLE PARAMETERS:
- k1: Transfer rate from compartment 1 to 2
- k2: Transfer rate from compartment 2 to 1
UNIDENTIFIABLE PARAMETERS:
(None)
IDENTIFIABLE COMBINATIONS:
(All individual parameters are identifiable)
OBSERVABILITY ANALYSIS:
- Initial condition x1(0): Identifiable
- Initial condition x2(0): Not directly observable
SUMMARY:
This simple linear system has excellent identifiability properties.
Both rate parameters can be uniquely determined from measurements
of compartment 1, provided both compartments contain non-zero amounts.
Technical Details
Check results/simple_linear/nullspace_analysis.txt
:
NULLSPACE ANALYSIS RESULTS
========================
Observability Matrix:
[x1]
[-k1*x1 + k2*x2]
Identifiability Matrix (Jacobian):
[0, 0]
[-x1, x2]
Matrix Rank: 2 (when x1 ≠ 0 and x2 ≠ 0)
Nullspace Dimension: 0
INTERPRETATION:
- All parameters are structurally identifiable
- No parameter combinations are needed
- Identifiability depends on having non-zero states
Visualizations
The visualization folder contains:
results/simple_linear/visualizations/2d_projections/k1_vs_k2.png
results/simple_linear/visualizations/parameter_graph.png
The parameter graph shows two isolated nodes (k1 and k2) since they're independently identifiable.
Experimental Design Insights
What This Analysis Tells Us
- Measurement strategy: Measuring only \(x_1\) is sufficient for parameter identification
- Initial conditions: Need non-zero amounts in both compartments initially
- Experiment duration: Need to observe the system long enough to see transfer dynamics
- Data quality: Both parameters are identifiable, so focus on measurement precision
Practical Recommendations
Based on the identifiability analysis:
- Design experiments with appropriate initial conditions: \(x_1(0) > 0\), \(x_2(0) > 0\)
- Collect time-series data that captures the dynamic transfer between compartments
- Focus on measurement accuracy of compartment 1 since that's what determines identifiability
- Consider adding measurements of compartment 2 for improved parameter precision (though not necessary for identifiability)
Modifying the Example
Adding Complexity
Let's explore how the analysis changes with modifications:
Variant 1: Unknown Initial Condition
Modify the model to treat initial conditions as unknown parameters:
# Add initial condition parameters
x1_0 = sym.Symbol('x1_0') # Initial amount in compartment 1
x2_0 = sym.Symbol('x2_0') # Initial amount in compartment 2
# Expanded parameter vector
p = [[k1], [k2], [x1_0], [x2_0]]
Re-run the analysis to see how this affects identifiability.
Variant 2: Two Outputs
Modify to observe both compartments:
This should improve the identifiability properties.
Variant 3: Input Addition
Add an external input to compartment 1:
# Add input
u1 = sym.Symbol('u1')
u = [u1]
# Modify dynamics
f = [
[-k1*x1 + k2*x2 + u1], # Input to compartment 1
[k1*x1 - k2*x2]
]
Comparison with Complex Models
Why Start Simple?
This simple example demonstrates that:
- Linear systems often have good identifiability properties
- Clear mathematical structure makes analysis interpretation straightforward
- Physical intuition aligns with mathematical results
- Baseline understanding prepares you for complex nonlinear cases
Next Steps
After mastering this simple example:
- Try the two-compartment model:
nullstrike C2M
- similar structure but with elimination - Explore nonlinear systems:
nullstrike calibration_single
- see how nonlinearity affects identifiability - Work with your own models: Apply the principles to your research domain
Exercise: Parameter Sensitivity
Exploration Tasks
- Modify parameter ranges in the configuration file and observe how it affects visualizations
- Change the observable output to \(h = [x_2]\) and see how identifiability changes
- Add an elimination term like \(-k_0 x_1\) to the first equation and analyze the new system
- Compare with analytical solutions - solve the linear system analytically and verify that the identified parameters make sense
Expected Learning
- Understanding how model structure affects identifiability
- Appreciation for the relationship between observability and identifiability
- Intuition for experimental design based on mathematical analysis
- Preparation for analyzing more complex systems
Summary
This simple linear example demonstrates:
- Basic NullStrike workflow: Model definition → Configuration → Analysis → Results
- Identifiability concepts: What it means for parameters to be identifiable
- Result interpretation: Understanding analysis output and physical meaning
- Experimental design: How identifiability analysis guides experiments
- Mathematical foundations: Observability matrices, Jacobians, and rank analysis
The key insight is that even simple systems provide valuable insights when analyzed systematically. This foundation prepares you to tackle more complex nonlinear systems where the relationship between model structure and identifiability becomes much more subtle.
Further Reading
- Two-Compartment Model: Similar structure with added complexity (see custom_models/C2M.py)
- Calibration Model: Nonlinear system with enzyme kinetics (see custom_models/calibration_single.py)
- Theory Overview: Mathematical foundations
- Results Interpretation: How to analyze output files
Building Intuition
Simple examples like this are invaluable for building intuition about identifiability analysis. Master the concepts here before moving to complex nonlinear systems.