Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
970e687
Initial commit. Works for closed loop regazzoni with target max LV pr…
aabrown100-git Feb 6, 2026
5befd2d
Remove units from plots
aabrown100-git Feb 6, 2026
e066823
Fix various bugs, add graceful cleanup if optimization is cut short b…
aabrown100-git Feb 6, 2026
88ee111
Add sensitivity analysis, remove conversion from BloodVesselCRL to Bl…
aabrown100-git Feb 6, 2026
9ed0612
Add tuning config files for Nelder-Mead and differential_evolution. W…
aabrown100-git Feb 6, 2026
ab468dd
Increase cardiac cycles to 30 to ensure limit cycle, and change outpu…
aabrown100-git Feb 6, 2026
a98490f
consolidate run_simulation between sensitivity.py and sv0d_tuner.py, …
aabrown100-git Feb 6, 2026
9d122e4
Remove extra printing, rename iteration to evaluation for clarity, al…
aabrown100-git Feb 7, 2026
c165e5a
Add warning if a parameter is near its bounds
aabrown100-git Feb 7, 2026
782aff9
Throw error if initial value of parameter is outside of bounds
aabrown100-git Feb 7, 2026
25e8050
reduce bound tolerance to 0.01
aabrown100-git Feb 7, 2026
b2f29ce
Add per generation history tracking for differential_evolution, adjus…
aabrown100-git Feb 7, 2026
f9a6bff
Remove pysvzerod from requirements and replace it with note about ins…
aabrown100-git Feb 7, 2026
560962c
Print number of works for parallel optimziation
aabrown100-git Feb 7, 2026
6b784c3
Add Sherlock job script for closed_loop_Regazzoni tuning
aabrown100-git Feb 7, 2026
89a0d28
Compute and show percent error against target value, save simulated v…
aabrown100-git Feb 8, 2026
d0eab85
Increase bounds for tuning_complex.yaml. Add LBFGS polish for differe…
aabrown100-git Feb 8, 2026
fd26390
Refactor objective.py, use only weighted sum of (potentially) normali…
aabrown100-git Feb 8, 2026
bdd693f
Allow user to set ranges or uncertainty for targets, modify objective…
aabrown100-git Feb 8, 2026
abe49b6
Simplify optimizer algorithm options by directly passing yaml entries…
aabrown100-git Feb 8, 2026
1c60df9
reduce job time to 1 hour
aabrown100-git Feb 8, 2026
e4cbbfb
Big refactor of optimization: Simplify callback code. Add option for …
aabrown100-git Feb 8, 2026
28fee00
When plotting results, change linestyle when colors repeat. Reorder m…
aabrown100-git Feb 10, 2026
1b66db4
Add tuning example with time series target, modify target_comparison …
aabrown100-git Feb 10, 2026
edb79c8
Adding time series volume targets for tuning_complex example. Also, a…
aabrown100-git Feb 10, 2026
ad54228
add right_heart_pa example
ncdorn Feb 11, 2026
e12e1ae
add cli and refactor src to svzerodtuner
ncdorn Feb 12, 2026
8e0b4a7
Merge branch 'master' of https://github.com/SimVascular/svZeroDSolver…
aabrown100-git Feb 18, 2026
f03e1ae
Merge branch 'SimVascular-master' into svZeroDTuner
aabrown100-git Feb 18, 2026
9435a45
Update closed_loop_Regazzoni example model.json with latest two hill …
aabrown100-git Feb 18, 2026
ec551c4
Update time series target ar_sys pressure using updated baseline with…
aabrown100-git Feb 19, 2026
5aef3d3
Big refactor of targets to allow custom targets. Now, all targets (an…
aabrown100-git Feb 19, 2026
2248d6f
Assign returned optimizer results to best_params and best_value
aabrown100-git Feb 19, 2026
c6fab3e
Make Expression more efficient by caching a compiled function
aabrown100-git Feb 19, 2026
ae4681d
Fix JSON formatting for example model.jsons
aabrown100-git Feb 19, 2026
7fd7f84
Adding clarifying output about initial parameter values
aabrown100-git Feb 23, 2026
d5c0f47
Adding closed_loop_Zingaro example
aabrown100-git Feb 23, 2026
d8156c5
Zingaro model json formatting, minor cosmetic changes
aabrown100-git Feb 23, 2026
643eef1
Adjust tuning_job parameters
aabrown100-git Feb 23, 2026
ea7b8e7
Forgot to uncomment algorithm
aabrown100-git Feb 23, 2026
7f8f2ab
Change objective function to compute sum of relative errors instead o…
aabrown100-git Feb 23, 2026
4834d3f
Add comment that we are using L1 error
aabrown100-git Feb 23, 2026
a1040fd
Adding option for L1 or L2 norm of relative errors
aabrown100-git Feb 24, 2026
fa050d9
Use L2 norm for Zingaro example
aabrown100-git Feb 24, 2026
96cd186
Fixing typo in R_VEN_PUL in Zingaro example
aabrown100-git Feb 24, 2026
7e95189
Remove in efficient concatenate within target loop
aabrown100-git Feb 24, 2026
a464413
Increase tuning_job.sh to use 2 nodes for faster optimization
aabrown100-git Feb 24, 2026
20d97a6
Revert to 1 node since multinode doesn't work, Increase to 1 hour., i…
aabrown100-git Feb 24, 2026
c1c3bab
Increase maxiter to 500, job time to 2 hours
aabrown100-git Feb 24, 2026
ad0b55f
Change pressure targets to aortic and pulmonary artery pressures (bef…
aabrown100-git Feb 24, 2026
0b3b6ad
Increase range for R_VEN_SYS
aabrown100-git Feb 24, 2026
7753674
Correct conversion of volume target from RR% to time
aabrown100-git Feb 24, 2026
47ef2a1
Add aortic and pulmonary valve peak flow rate targets (from Zingaro p…
aabrown100-git Feb 24, 2026
5053be9
Clean up volume conversion scripts
aabrown100-git Feb 24, 2026
87dcd90
Increase range on R_VEN_SYS, use differential evolution with popsize …
aabrown100-git Feb 24, 2026
98b1a66
Add support for log and max parameter scaling
aabrown100-git Feb 25, 2026
2ff9414
enhance expression handling for multiprocessing; improve optimizer wi…
ncdorn Feb 25, 2026
ed4b1f8
Add scalings to regazzoni/tuning_complex.
aabrown100-git Feb 25, 2026
c1d2caf
Use max scaling for regazzoni tuning_complex. Log scaling was not ver…
aabrown100-git Feb 25, 2026
688586e
Back to log scaling, increase maxiter to 500, popsize to 30, polish f…
aabrown100-git Feb 25, 2026
a194e72
Refactor svZeroDTuner configuration to replace 'uncertainty' with 're…
ncdorn Mar 4, 2026
ab5fd0a
Merge branch 'svZeroDTuner' of https://github.com/aabrown100-git/svZe…
ncdorn Mar 4, 2026
c4e4496
Merge branch 'SimVascular:master' into svZeroDTuner
aabrown100-git Mar 5, 2026
eb0fd17
address bugs in codex review
ncdorn Apr 8, 2026
82a65cd
improve target range validationand fix sensitivityanalyzer accumulati…
ncdorn Apr 8, 2026
aa08a6e
Update usage instructions and enhance command-line workflow in examples
ncdorn Apr 8, 2026
d138f35
remove svg from docs
ncdorn Apr 9, 2026
dbbfbcd
add pyyaml to windows CI
ncdorn Apr 16, 2026
1b8392f
Merge branch 'master' into svZeroDTuner
aabrown100-git Apr 24, 2026
dc2e2ba
Merge branch 'SimVascular:master' into svZeroDTuner
aabrown100-git Apr 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ jobs:
continue-on-error: true
shell: pwsh
run: |
python -m pip install --upgrade cmake cmake-setuptools numpy ninja pytest pandas graphviz networkx pydot
python -m pip install --upgrade cmake cmake-setuptools numpy ninja pytest pandas graphviz networkx pydot pyyaml

# keep MinGW/Strawberry off PATH (so Eigen won't try Fortran)
$pattern = '^(C:\\mingw64\\bin|C:\\tools\\mingw64\\bin|C:\\Strawberry\\c\\bin|C:\\Strawberry\\perl\\site\\bin|C:\\Strawberry\\perl\\bin)$'
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ build*/
# Node modules (for directed graph visualization)
node_modules/

# Allow csv for target files in svZeroDTuner examples
!applications/svZeroDTuner/examples/**/targets/**/*.csv

# Virtual environments
venv/
.venv/
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
<div align="center">
<h1>svZeroDSolver</h1>

[![Test Status](https://github.com/simvascular/svZeroDSolver/actions/workflows/test.yml/badge.svg)](https://github.com/simvascular/svZeroDSolver/actions)
[![codecov](https://codecov.io/gh/SimVascular/svZeroDSolver/graph/badge.svg?token=FQKC9L5I0W)](https://codecov.io/gh/SimVascular/svZeroDSolver)
[![Latest Release](https://img.shields.io/github/v/release/simvascular/svZeroDSolver?label=latest)](https://github.com/simvascular/svZeroDSolver/releases/latest)
![Platform](https://img.shields.io/badge/platform-macOS%20|%20Ubuntu-blue)
Comment on lines 4 to 6
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leave the badges?


Expand All @@ -15,6 +13,7 @@ You can find more information under the following links:

* [**Documentation**](https://simvascular.github.io/svZeroDSolver)
* [**Developer Guide**](https://simvascular.github.io/svZeroDSolver/developer_guide.html)
* [**svZeroDTuner Guide**](https://simvascular.github.io/svZeroDSolver/tuner.html)
* [**Bug Reports**](https://github.com/simvascular/svZeroDSolver/issues)
* [**Forum**](https://github.com/simvascular/svZeroDSolver/discussions)
* [**About SimVascular**](https://simvascular.github.io)
6 changes: 6 additions & 0 deletions applications/svZeroDTuner/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
baseline_results/
optimization_results*/
sensitivity_results/
reference_codes/


279 changes: 279 additions & 0 deletions applications/svZeroDTuner/examples/closed_loop_Regazzoni/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
"""
sv0D Tuning Framework - Regazzoni closed-loop model example.

This script provides three modes:
1. BASELINE MODE: Run the initial model and save all results for inspection
2. SENSITIVITY MODE: Run correlation-based sensitivity screening
3. OPTIMIZE MODE: Run optimization using targets specified in tuning.yaml

Recommended command-line workflow:
Baseline: python -c 'from main import run_baseline; run_baseline("model.json")'
Optimize: svzerodtuner optimize tuning_nelder_mead.yaml
Sensitivity: svzerodtuner sensitivity sensitivity.yaml
"""

import os
import sys
import numpy as np
import pandas as pd
import pysvzerod

# Add src to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../..'))

import json
from svzerodtuner.sv0d_tuner import SV0DTuner
from svzerodtuner.visualization import plot_simulation_results
from svzerodtuner.sensitivity import SensitivityAnalyzer


def run_baseline(config_file):
"""
Run the baseline simulation and save all results for user inspection.

This function:
- Runs the initial model.json simulation
- Saves all available outputs to baseline_results.csv
- Displays summary statistics (min, max, mean) for each output
- User can then inspect these results and specify targets in tuning_config.yaml
"""
print("="*70)
print("BASELINE SIMULATION")
print("="*70)
print()

if not os.path.exists(config_file):
print(f"ERROR: Config file not found: {config_file}")
return

print(f"Running simulation with: {config_file}")
print()

# Run baseline simulation
try:
solver = pysvzerod.Solver(config_file)
solver.run()
print("✓ Simulation completed successfully\n")
except Exception as e:
print(f"ERROR running simulation: {e}")
return

# Extract all results
times = solver.get_times()
full_results = solver.get_full_result()
result_names = full_results['name'].unique()

print(f"Found {len(result_names)} output variables")
print()

# Create results DataFrame with all outputs
results_data = {'time': times}
summary_stats = []

for name in result_names:
try:
values = solver.get_single_result(name)
results_data[name] = values

# Calculate statistics
stats = {
'output_name': name,
'min': np.min(values),
'max': np.max(values),
'mean': np.mean(values),
'std': np.std(values)
}
summary_stats.append(stats)
except Exception as e:
print(f"Warning: Could not extract {name}: {e}")

# Create baseline_results directory
output_dir = 'baseline_results'
os.makedirs(output_dir, exist_ok=True)

# Save full results to CSV
results_df = pd.DataFrame(results_data)
baseline_file = os.path.join(output_dir, 'baseline_results.csv')
results_df.to_csv(baseline_file, index=False)
print(f"✓ Saved full time series to: {baseline_file}")

# Save summary statistics
summary_df = pd.DataFrame(summary_stats)
summary_file = os.path.join(output_dir, 'baseline_summary.csv')
summary_df.to_csv(summary_file, index=False)
print(f"✓ Saved summary statistics to: {summary_file}")
print()

# Display summary statistics
print("="*70)
print("BASELINE RESULTS SUMMARY")
print("="*70)
print()
print(f"{'Output Variable':<40} {'Min':>12} {'Max':>12} {'Mean':>12}")
print("-"*70)

for stats in summary_stats:
print(f"{stats['output_name']:<40} {stats['min']:>12.4e} "
f"{stats['max']:>12.4e} {stats['mean']:>12.4e}")

# Generate plots
print()
print("Generating plots...")
plot_simulation_results(results_df, summary_df, output_dir, title_prefix="Baseline")

print()
print("="*70)
print("NEXT STEPS:")
print("="*70)
print(f"1. Inspect {output_dir}/baseline_results.csv and baseline_summary.csv")
print(f"2. View plots in {output_dir}/ to visualize the outputs")
print("3. Choose which outputs you want to target")
print("4. Update tuning.yaml with your desired targets")
print("5. Run svzerodtuner optimize <tuning_config.yaml>")
print("="*70)
print()


def run_optimization(config_file):
"""
Run optimization using targets specified in config_file.
"""
print("="*70)
print("OPTIMIZATION")
print("="*70)
print()

if not os.path.exists(config_file):
print(f"ERROR: Config file not found: {config_file}")
print(f"Please create {config_file} with your optimization settings.")
return

print(f"Using configuration: {config_file}")
print()

# Initialize tuner
try:
tuner = SV0DTuner(config_file)
except Exception as e:
print(f"ERROR loading configuration: {e}")
return

# Run optimization
print("Starting optimization...")
print("="*70)
print()

try:
results = tuner.optimize()
except Exception as e:
print(f"ERROR during optimization: {e}")
return

def run_sensitivity(config_file):
"""
Run correlation-based sensitivity screening.

This function:
- Performs global sensitivity analysis on specified parameters
- Computes first-order and total-order screening scores
- Identifies which parameters most influence each quantity of interest
- Saves results, plots, and summary statistics
"""
print("="*70)
print("SENSITIVITY ANALYSIS")
print("="*70)
print()

if not os.path.exists(config_file):
print(f"ERROR: Config file not found: {config_file}")
print(f"Please create {config_file} with your sensitivity analysis settings.")
print(f"See sensitivity.yaml.example for template.")
return

print(f"Using configuration: {config_file}")
print()

# Initialize sensitivity analyzer
try:
analyzer = SensitivityAnalyzer(config_file)
except Exception as e:
print(f"ERROR loading configuration: {e}")
import traceback
traceback.print_exc()
return

# Run sensitivity analysis
try:
results = analyzer.run()
except Exception as e:
print(f"ERROR during sensitivity analysis: {e}")
import traceback
traceback.print_exc()
return

# Save results
try:
analyzer.save_results()
except Exception as e:
print(f"ERROR saving results: {e}")
import traceback
traceback.print_exc()
return

# Print summary
print()
print("="*70)
print("SENSITIVITY ANALYSIS COMPLETE")
print("="*70)
print()
print("Summary of Results:")
print("-"*70)

for qoi_key, qoi_results in results.items():
print(f"\n{qoi_key}:")
print(f" Range: [{qoi_results['min']:.4e}, {qoi_results['max']:.4e}]")
print(f" Mean ± Std: {qoi_results['mean']:.4e} ± {qoi_results['std']:.4e}")
print(f"\n Most influential parameters (first-order indices):")

# Sort by influence
first_order = qoi_results['first_order']
sorted_params = sorted(first_order.items(), key=lambda x: abs(x[1]), reverse=True)

for param, value in sorted_params:
print(f" {param:<30} {value:>8.4f}")

print()
print(f"Results saved to: {analyzer.output_config.get('directory', 'sensitivity_results')}")
print("="*70)
print()


def main():
"""
Main function.

INSTRUCTIONS:
============
Uncomment ONE of the following modes to run:

MODE 1: BASELINE - Run initial simulation and save results for inspection
MODE 2: OPTIMIZE - Run optimization with targets from tuning.yaml
MODE 3: SENSITIVITY - Run global sensitivity analysis with sensitivity.yaml
"""
# Change to script directory
os.chdir(os.path.dirname(os.path.abspath(__file__)))

# ============================================================================
# SELECT MODE: Uncomment ONE of the following
# ============================================================================

#run_baseline("model.json") # MODE 1: Run baseline and save results
#run_sensitivity("sensitivity.yaml") # MODE 2: Run sensitivity analysis
run_optimization("tuning_nelder_mead.yaml") # MODE 3: Run optimization with tuning.yaml


# ============================================================================


if __name__ == "__main__":
main()
Loading
Loading