Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
0380cd0
NPSAH v0.1
Siel Dec 13, 2025
38cf249
WIP: Testing new algorithms
Siel Dec 14, 2025
2637a12
basic struct
Siel Dec 15, 2025
dd1b093
wip: SAEM's outputs
Siel Jan 2, 2026
4d3040d
wip: cleaning outputs
Siel Jan 2, 2026
411833d
wip: cleaning up
Siel Jan 3, 2026
c529859
wip: cleaning up
Siel Jan 3, 2026
270c364
playing with non-parametric algorithms
Siel Jan 22, 2026
394c6b5
use git dependency
Siel Feb 5, 2026
cf4476d
use git dependency
Siel Feb 5, 2026
cc305bd
wip: saving progress so far
Siel Mar 6, 2026
c11d38f
gh dependency
Siel Mar 10, 2026
bd44d55
Merge branch 'main' into parametric
Siel Mar 25, 2026
fda9b20
merged main
Siel Mar 25, 2026
388a950
basic saem example seems to be working
Siel Mar 26, 2026
013bec5
Full redesign of PMcore's structure. This was done because in the pre…
Siel Mar 27, 2026
dd4be93
remove bloat
Siel Mar 27, 2026
708d8c7
cleaning a bit more
Siel Mar 27, 2026
8c90072
remove bloat
Siel Mar 27, 2026
f9064aa
update .gitignore
Siel Mar 27, 2026
bd7a88b
fmt
Siel Mar 27, 2026
60d74c8
chore: merged main into unified-platform
Siel Apr 1, 2026
c046f1a
chore: fmt
Siel Apr 1, 2026
3d0cfa8
wip:saving current state
Siel Apr 9, 2026
5922da4
use release pharmsol
Siel Apr 11, 2026
0a4d961
chore: sync local changes for unified-platform-m1
Siel Apr 13, 2026
15c26b4
Merge origin/main into feature/unified-platform-m1-main-prep
Siel Apr 28, 2026
bb4b6d4
Extract unified platform structure branch
Siel Apr 28, 2026
bf5f52c
Align structure split with prep tree
Siel Apr 28, 2026
bb25723
Run cargo fmt
Siel Apr 28, 2026
7d89148
chore: remove output data
Siel Apr 29, 2026
35d08de
chore: remove output data
Siel Apr 29, 2026
6105d5b
feat: using latest version of pharmsol + clippy
Siel May 1, 2026
3742f22
chore: fmt
Siel May 1, 2026
bf9e45c
feat: latest version of the API
Siel May 1, 2026
cbeb860
feat: latest version of the API
Siel May 2, 2026
e8576cd
chore: update to pharmsol 0.27.0
Siel May 14, 2026
9b06db9
chore: update benchmark
Siel May 14, 2026
668dd30
feature: support for pharmsol 0.27.1
Siel May 14, 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
24 changes: 20 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@
/Cargo.lock
*.log
theta.csv
cycles.csv
pred.csv
obs.csv
time.csv
n_psi.csv
psi.csv
r.csv
correlation.csv
/docs
diagnostics.json
predictions.csv
summary.csv
summary.json
iterations.csv
population.csv
shrinkage.csv
statistics.csv
posterior.csv
simulation_output.csv
/examples/rosuva/*
Expand All @@ -17,6 +25,8 @@ simulation_output.csv
/examples/data/iohexol*
/examples/data/rosuva*
/examples/data/vori*
/examples/paper_benchmarks
/examples/*/output
/.idea
stop
.vscode
Expand All @@ -28,7 +38,13 @@ settings.json
log.txt
op.csv
*results.txt
covs.csv
covariates.csv
individual_effects.csv
individual_parameters.csv
residual_error.csv
error_theta.csv
lcov.info
Fortran/
Fortran/
paper/
docs/
examples/**/outputs/
16 changes: 14 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,31 @@ tracing-subscriber = { version = "0.3.19", features = [
"time",
] }
faer = "0.24.0"
pharmsol = "=0.26.1"
pharmsol = { version = "=0.27.1"}
anyhow = "1.0.100"
rayon = "1.10.0"
rand = "0.10.1"
cobyla = "0.8.0"
rand_distr = "0.6.0"
statrs = "0.18"

[features]
default = []
exa = ["pharmsol/exa"]
json = []
exa = []
dsl-core = ["pharmsol/dsl-core"]
dsl-jit = ["pharmsol/dsl-jit"]
dsl-aot = ["pharmsol/dsl-aot"]
dsl-aot-load = ["pharmsol/dsl-aot-load"]
dsl-wasm = ["pharmsol/dsl-wasm"]

[profile.release]
codegen-units = 1
opt-level = 3

[profile.test]
inherits = "release"

[dev-dependencies]
approx = "0.5.1"
criterion = { version = "0.8" }
Expand Down
105 changes: 51 additions & 54 deletions benches/bimodal_ke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,83 +5,80 @@ use pmcore::prelude::*;
use std::hint::black_box;

fn create_equation() -> equation::ODE {
equation::ODE::new(
|x, p, _t, dx, b, rateiv, _cov| {
fetch_params!(p, ke, _v);
dx[0] = -ke * x[0] + rateiv[1] + b[1];
ode! {
name: "bimodal_ke",
params: [ke, v],
states: [central],
outputs: [outeq_1],
routes: [
infusion(input_1) -> central,
],
diffeq: |x, _t, dx| {
dx[central] = -ke * x[central];
},
|_p, _t, _cov| lag! {},
|_p, _t, _cov| fa! {},
|_p, _t, _cov, _x| {},
|x, p, _t, _cov, y| {
fetch_params!(p, _ke, v);
y[1] = x[0] / v;
out: |x, _t, y| {
y[outeq_1] = x[central] / v;
},
)
}
}

fn create_parameters() -> Parameters {
Parameters::new()
.add("ke", 0.001, 3.0)
.add("v", 25.0, 250.0)
}

fn create_error_models() -> Result<AssayErrorModels> {
Ok(AssayErrorModels::new().add(
1,
AssayErrorModel::additive(ErrorPoly::new(0.0, 0.5, 0.0, 0.0), 0.0),
)?)
fn create_error_model() -> AssayErrorModel {
AssayErrorModel::additive(ErrorPoly::new(0.0, 0.5, 0.0, 0.0), 0.0)
}

fn load_data() -> Result<data::Data> {
Ok(data::read_pmetrics("examples/bimodal_ke/bimodal_ke.csv")?)
}

fn setup_with_algorithm(algorithm: Algorithm) -> Result<(Settings, equation::ODE, data::Data)> {
let params = create_parameters();
let ems = create_error_models()?;

let mut settings = Settings::builder()
.set_algorithm(algorithm)
.set_parameters(params)
.set_error_models(ems)
.build();

settings.set_cycles(1000);
settings.set_prior(Prior::sobol(2048, 22));
settings.disable_output();
settings.set_progress(false);

fn setup_npag() -> Result<EstimationProblem<equation::ODE>> {
let data = load_data()?;
Ok((settings, create_equation(), data))
EstimationProblem::builder(create_equation(), data)
.parameter(Parameter::bounded("ke", 0.001, 3.0))?
.parameter(Parameter::bounded("v", 25.0, 250.0))?
.method(Npag::new())
.error("outeq_1", create_error_model())?
.cycles(1000)
.progress(false)
.prior(Prior::sobol(2048, 22))
.build()
}

fn setup_npag() -> Result<(Settings, equation::ODE, data::Data)> {
setup_with_algorithm(Algorithm::NPAG)
}

fn setup_npod() -> Result<(Settings, equation::ODE, data::Data)> {
setup_with_algorithm(Algorithm::NPOD)
fn setup_npod() -> Result<EstimationProblem<equation::ODE>> {
let data = load_data()?;
EstimationProblem::builder(create_equation(), data)
.parameter(Parameter::bounded("ke", 0.001, 3.0))?
.parameter(Parameter::bounded("v", 25.0, 250.0))?
.method(Npod::new())
.error("outeq_1", create_error_model())?
.cycles(1000)
.progress(false)
.prior(Prior::sobol(2048, 22))
.build()
}

fn setup_postprob() -> Result<(Settings, equation::ODE, data::Data)> {
setup_with_algorithm(Algorithm::POSTPROB)
fn setup_postprob() -> Result<EstimationProblem<equation::ODE>> {
let data = load_data()?;
EstimationProblem::builder(create_equation(), data)
.parameter(Parameter::bounded("ke", 0.001, 3.0))?
.parameter(Parameter::bounded("v", 25.0, 250.0))?
.method(PostProb::new())
.error("outeq_1", create_error_model())?
.cycles(1000)
.progress(false)
.prior(Prior::sobol(2048, 22))
.build()
}

fn benchmark_algorithm<F>(c: &mut Criterion, bench_name: &str, setup_fn: F)
where
F: Fn() -> Result<(Settings, equation::ODE, data::Data)>,
F: Fn() -> Result<EstimationProblem<equation::ODE>>,
{
let (settings, eq, data) = setup_fn().unwrap();
let problem = setup_fn().unwrap();

c.bench_function(bench_name, |b| {
b.iter_with_setup(
|| (settings.clone(), eq.clone(), data.clone()),
|(s, e, d)| {
let mut algorithm = dispatch_algorithm(s, e, d).unwrap();
let result = algorithm.fit().unwrap();
black_box(result)
},
|| problem.clone(),
|problem| black_box(problem.fit().unwrap()),
)
});
}
Expand Down
49 changes: 18 additions & 31 deletions examples/bestdose.rs
Original file line number Diff line number Diff line change
@@ -1,40 +1,34 @@
use anyhow::Result;
use pmcore::bestdose::{BestDosePosterior, DoseRange, Target};

use pmcore::bestdose::{BestDoseConfig, BestDosePosterior, DoseRange, Target};
use pmcore::prelude::*;
use pmcore::routines::initialization::parse_prior;

fn main() -> Result<()> {
// Example model
let eq = ode! {
diffeq: |x, p, _t, dx, b, _rateiv, _cov| {
// fetch_cov!(cov, t, wt);
fetch_params!(p, ke, _v);
dx[0] = -ke * x[0] + b[0];
name: "bestdose_one_compartment",
params: [ke, v],
states: [central],
outputs: [cp],
routes: [
bolus(dose) -> central,
],
diffeq: |x, _t, dx| {
dx[central] = -ke * x[central];
},
out: |x, p, _t, _cov, y| {
fetch_params!(p, _ke, v);
y[0] = x[0] / v;
out: |x, _t, y| {
y[cp] = x[central] / v;
},
};

let params = Parameters::new()
.add("ke", 0.001, 3.0)
.add("v", 25.0, 250.0);
let parameter_space = ParameterSpace::new()
.add(Parameter::bounded("ke", 0.001, 3.0))
.add(Parameter::bounded("v", 25.0, 250.0));

let ems = AssayErrorModels::new().add(
0,
AssayErrorModel::additive(ErrorPoly::new(0.0, 0.20, 0.0, 0.0), 0.0),
)?;

// Make settings
let mut settings = Settings::builder()
.set_algorithm(Algorithm::NPAG)
.set_parameters(params)
.set_error_models(ems.clone())
.build();

settings.disable_output();
let config = BestDoseConfig::new(parameter_space.clone(), ems.clone()).with_progress(false);

// Generate a patient with known parameters
// Ke = 0.5, V = 50
Expand Down Expand Up @@ -71,21 +65,14 @@ fn main() -> Result<()> {
.observation(18.0, conc(6.0, 75.0) + conc(18.0, 150.0), 0)
.build();

let (theta, prior) = parse_prior(
&"examples/bimodal_ke/output/theta.csv".to_string(),
&settings,
)
.unwrap();
let (theta, prior) = read_prior("examples/bimodal_ke/output/theta.csv", &parameter_space)?;

// Example usage - two-stage API:
// Stage 1: Compute posterior (expensive, done once)
// Stage 2: Optimize doses (can be called multiple times with different params)
let posterior = BestDosePosterior::compute(
&theta,
&prior.unwrap(),
Some(past_data.clone()), // Optional: past data for Bayesian updating
eq.clone(),
settings.clone(),
config.clone(),
)?;

println!("Optimizing dose...");
Expand Down
Loading
Loading