Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
51f24e2
Refactoring
mhovd May 25, 2026
47ce30c
Refactor
mhovd May 25, 2026
786d154
Rename method
mhovd May 26, 2026
c5121c5
Rename method
mhovd May 26, 2026
d8529a8
Refactor
mhovd May 26, 2026
e62a64c
Refactor
mhovd May 27, 2026
91ff0f0
Refactor algorithm
mhovd May 27, 2026
5ca4d97
Update examples
mhovd May 27, 2026
31b6691
Refactor
mhovd May 27, 2026
5cdcba5
Refactor
mhovd May 27, 2026
f53b20f
Delete vanco_sde.zip
mhovd May 27, 2026
3b8ed67
Refactor
mhovd May 27, 2026
4d391f4
WIP Refactor
mhovd May 27, 2026
e62e426
WIP
mhovd May 27, 2026
37d9ccb
Refactor
mhovd May 27, 2026
45ddb5c
Refactor
mhovd May 28, 2026
3618726
Doc
mhovd May 28, 2026
e7aa971
Remove dead code
mhovd May 28, 2026
fa43d91
Clean up the builder
mhovd May 28, 2026
a3a9fa6
Clean up benchmarks
mhovd May 28, 2026
7243b9c
Docs
mhovd May 28, 2026
dd74646
Major refactor
mhovd May 28, 2026
fd56568
Docs
mhovd May 28, 2026
5950a3a
BestDose
mhovd May 28, 2026
601b9ed
Tests and examples
mhovd May 28, 2026
769a430
Update benchmark
mhovd May 28, 2026
191376e
Refactoring examples
mhovd May 28, 2026
935e6eb
Refactor
mhovd May 28, 2026
096422c
NPAG is working
mhovd May 28, 2026
eda3028
Docs
mhovd May 28, 2026
dc5f475
NPOD is working
mhovd May 28, 2026
0be4f0d
Docs
mhovd May 28, 2026
ca7dde2
Refactor theta reading from file
mhovd May 28, 2026
add8213
Refactor parameters
mhovd Jun 8, 2026
61e9ea9
Add validation to builder
mhovd Jun 8, 2026
e9d4957
Refactor modules
mhovd Jun 8, 2026
9a0b237
Update examples
mhovd Jun 8, 2026
25d0350
Clean up lib.rs
mhovd Jun 8, 2026
b18c03a
WIP: Change how prior is defined
mhovd Jun 8, 2026
6eafc67
Update parameter_space.rs
mhovd Jun 8, 2026
797ff7d
Result
mhovd Jun 11, 2026
7e2198b
Rename error to error_model
mhovd Jun 12, 2026
5a88470
Refactor the EstimationProblem around Theta over Parameters
mhovd Jun 12, 2026
1dc964c
Clean up dead code
mhovd Jun 12, 2026
99d2dc0
Refactor validate_psi
mhovd Jun 13, 2026
1e5fe0d
Refactor fit_with
mhovd Jun 13, 2026
cafeff5
Remove possibility for no-ops
mhovd Jun 13, 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
96 changes: 55 additions & 41 deletions benches/bimodal_ke.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::Result;
use criterion::{criterion_group, criterion_main, Criterion};
use pmcore::prelude::*;
use pmcore::{algorithms::Algorithm, prelude::*};

use std::hint::black_box;

Expand All @@ -22,74 +22,87 @@ fn create_equation() -> equation::ODE {
}
}

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_npag() -> Result<EstimationProblem<equation::ODE>> {
fn setup_npag() -> Result<EstimationProblem<equation::ODE, NonParametric>> {
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(Npag::new())
.error("outeq_1", create_error_model())?
.progress(false)
.prior(Prior::sobol(2048, 22))
.build()
let parameters = ParameterSpace::bounded()
.add("ke", 0.001, 3.0)
.add("v", 25.0, 250.0);
let prior = Theta::sobol_default(&parameters)?;
let error_models = AssayErrorModels::new().add(
"outeq_1",
AssayErrorModel::additive(ErrorPoly::new(0.0, 0.5, 0.0, 0.0), 0.0),
)?;
EstimationProblem::nonparametric(create_equation(), data, prior, error_models)
}

fn setup_npod() -> Result<EstimationProblem<equation::ODE>> {
fn setup_npod() -> Result<EstimationProblem<equation::ODE, NonParametric>> {
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())?
.progress(false)
.prior(Prior::sobol(2048, 22))
.build()
let parameters = ParameterSpace::bounded()
.add("ke", 0.001, 3.0)
.add("v", 25.0, 250.0);
let prior = Theta::sobol_default(&parameters)?;
let error_models = AssayErrorModels::new().add(
"outeq_1",
AssayErrorModel::additive(ErrorPoly::new(0.0, 0.5, 0.0, 0.0), 0.0),
)?;
EstimationProblem::nonparametric(create_equation(), data, prior, error_models)
}

fn setup_postprob() -> Result<EstimationProblem<equation::ODE>> {
fn setup_postprob() -> Result<EstimationProblem<equation::ODE, NonParametric>> {
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())?
.progress(false)
.prior(Prior::sobol(2048, 22))
.build()
let parameters = ParameterSpace::bounded()
.add("ke", 0.001, 3.0)
.add("v", 25.0, 250.0);
let prior = Theta::sobol_default(&parameters)?;
let error_models = AssayErrorModels::new().add(
"outeq_1",
AssayErrorModel::additive(ErrorPoly::new(0.0, 0.5, 0.0, 0.0), 0.0),
)?;
EstimationProblem::nonparametric(create_equation(), data, prior, error_models)
}

fn benchmark_algorithm<F>(c: &mut Criterion, bench_name: &str, setup_fn: F)
fn benchmark_algorithm<F, A>(c: &mut Criterion, bench_name: &str, setup_fn: F, config: A)
where
F: Fn() -> Result<EstimationProblem<equation::ODE>>,
F: Fn() -> Result<EstimationProblem<equation::ODE, NonParametric>>,
A: Algorithm<equation::ODE, NonParametric> + Clone,
{
let problem = setup_fn().unwrap();

c.bench_function(bench_name, |b| {
b.iter_with_setup(
|| problem.clone(),
|problem| black_box(problem.fit().unwrap()),
|| setup_fn().unwrap(),
|problem| black_box(problem.fit_with(config.clone()).unwrap()),
)
});
}

fn benchmark_bimodal_ke_npag(c: &mut Criterion) {
benchmark_algorithm(c, "bimodal_ke_npag", setup_npag);
benchmark_algorithm(
c,
"bimodal_ke_npag",
setup_npag,
NonParametricAlgorithm::npag(),
);
}

fn benchmark_bimodal_ke_npod(c: &mut Criterion) {
benchmark_algorithm(c, "bimodal_ke_npod", setup_npod);
benchmark_algorithm(
c,
"bimodal_ke_npod",
setup_npod,
NonParametricAlgorithm::npod(),
);
}

fn benchmark_bimodal_ke_postprob(c: &mut Criterion) {
benchmark_algorithm(c, "bimodal_ke_postprob", setup_postprob);
benchmark_algorithm(
c,
"bimodal_ke_postprob",
setup_postprob,
NonParametricAlgorithm::npmap(),
);
}

criterion_group! {
Expand All @@ -98,3 +111,4 @@ criterion_group! {
targets = benchmark_bimodal_ke_npag, benchmark_bimodal_ke_npod, benchmark_bimodal_ke_postprob
}
criterion_main!(benches);

9 changes: 5 additions & 4 deletions examples/bestdose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ fn main() -> Result<()> {
},
};

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

let ems = AssayErrorModels::new().add(
0,
Expand Down Expand Up @@ -65,7 +65,8 @@ fn main() -> Result<()> {
.observation(18.0, conc(6.0, 75.0) + conc(18.0, 150.0), 0)
.build();

let (theta, prior) = read_prior("examples/bimodal_ke/output/theta.csv", &parameter_space)?;
let (theta, prior) =
Theta::from_file("examples/bimodal_ke/output/theta.csv", &parameter_space)?;

let posterior = BestDosePosterior::compute(
&theta,
Expand Down
14 changes: 8 additions & 6 deletions examples/bestdose_auc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ fn main() -> Result<()> {
};

// Minimal parameter ranges
let parameter_space = ParameterSpace::new()
.add(Parameter::bounded("ke", 0.001, 3.0))
.add(Parameter::bounded("v", 25.0, 250.0));
let parameter_space = ParameterSpace::<BoundedParameter>::new()
.add("ke", 0.001, 3.0)
.add("v", 25.0, 250.0);

let ems = AssayErrorModels::new().add(
0,
Expand All @@ -43,7 +43,10 @@ fn main() -> Result<()> {

// Load realistic prior from previous NPAG run (47 support points)
println!("Loading prior from bimodal_ke example...");
let (theta, prior) = read_prior("examples/bimodal_ke/output/theta.csv", &parameter_space)?;
let (theta, prior) = Theta::from_file(
"examples/bimodal_ke/output/theta.csv",
&parameter_space,
)?;
let weights = prior.as_ref().unwrap();

println!("Prior: {} support points\n", theta.matrix().nrows());
Expand Down Expand Up @@ -118,9 +121,8 @@ fn main() -> Result<()> {
}
}

// =========================================================================
// EXAMPLE 2: Interval AUC (AUCFromLastDose)
// =========================================================================

println!("\n\n");
println!("════════════════════════════════════════════════════════");
println!(" EXAMPLE 2: Interval AUC (AUCFromLastDose)");
Expand Down
11 changes: 7 additions & 4 deletions examples/bestdose_bounds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ fn main() -> Result<()> {
},
};

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

let ems = AssayErrorModels::new().add(
0,
Expand All @@ -40,7 +40,10 @@ fn main() -> Result<()> {

// Load realistic prior from previous NPAG run
println!("Loading prior from bimodal_ke example...");
let (theta, prior) = read_prior("examples/bimodal_ke/output/theta.csv", &parameter_space)?;
let (theta, prior) = Theta::from_file(
"examples/bimodal_ke/output/theta.csv",
&parameter_space,
)?;
let weights = prior.as_ref().unwrap();

println!("Prior: {} support points\n", theta.matrix().nrows());
Expand Down
38 changes: 17 additions & 21 deletions examples/bestdose_cov.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use anyhow::Result;
use pmcore::bestdose; // bestdose new
// use pmcore::bestdose::bestdose_old as bestdose; // bestdose old

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

fn main() -> Result<()> {
Expand All @@ -23,17 +21,16 @@ fn main() -> Result<()> {
},
};

let parameter_space = ParameterSpace::new()
.add(Parameter::bounded("ke", 0.001, 3.0))
.add(Parameter::bounded("v", 25.0 / 70.0, 250.0 / 70.0));
let parameter_space = ParameterSpace::<BoundedParameter>::new()
.add("ke", 0.001, 3.0)
.add("v", 25.0 / 70.0, 250.0 / 70.0);

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

let config =
bestdose::BestDoseConfig::new(parameter_space.clone(), ems.clone()).with_progress(false);
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 @@ -93,29 +90,22 @@ fn main() -> Result<()> {
.observation(18.0, conc(6.0, 75.0) + conc(18.0, 150.0), 0)
.build();

let (mut theta, prior) = read_prior("examples/bimodal_ke/output/theta.csv", &parameter_space)?;
let (mut theta, prior) = Theta::from_file(
"examples/bimodal_ke/output/theta.csv",
&parameter_space,
)?;

let m_t = theta.matrix_mut();
for i in 0..m_t.nrows() {
m_t[(i, 1)] /= 70.0;
}

// Example usage - using new() constructor which calculates NPAGFULL11 posterior
// max_cycles controls NPAGFULL refinement:
// 0 = NPAGFULL11 only (fast but less accurate)
// 100 = moderate refinement
// 500 = full refinement (Fortran default, slow but most accurate)
let problem = bestdose::BestDoseProblem::new(
let posterior = BestDosePosterior::compute(
&theta,
&prior.unwrap(),
Some(past_data.clone()), // Optional: past data for Bayesian updating
target_data.clone(),
None,
eq.clone(),
bestdose::DoseRange::new(0.0, 300.0),
0.0,
config.clone(),
bestdose::Target::Concentration, // Target concentrations (not AUCs)
)?;

println!("Optimizing dose...");
Expand All @@ -125,7 +115,13 @@ fn main() -> Result<()> {

for bias_weight in &bias_weights {
println!("Running optimization with bias weight: {}", bias_weight);
let optimal = problem.clone().with_bias_weight(*bias_weight).optimize()?;
let optimal = posterior.optimize(
target_data.clone(),
None,
DoseRange::new(0.0, 300.0),
*bias_weight,
Target::Concentration,
)?;
results.push((bias_weight, optimal));
}

Expand Down
27 changes: 16 additions & 11 deletions examples/bimodal_ke/main.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use anyhow::Result;
use pmcore::{output::logging::Logger, prelude::*};
use pmcore::prelude::*;

fn main() -> Result<()> {
Logger::new().init()?;
Logger::new().stdout(true).init()?;

let eq = ode! {
name: "bimodal_ke",
Expand All @@ -23,15 +23,20 @@ fn main() -> Result<()> {

let data = data::read_pmetrics("examples/bimodal_ke/bimodal_ke.csv")?;

let _result = EstimationProblem::builder(eq, data)
.method(Npag::default())
.parameter(Parameter::bounded("ke", 0.001, 3.0))?
.parameter(Parameter::bounded("v", 25.0, 250.0))?
.error(
"outeq_1",
AssayErrorModel::additive(ErrorPoly::new(0.0, 0.5, 0.0, 0.0), 0.0),
)?
.fit()?;
let parameters = ParameterSpace::bounded()
.add("ke", 0.001, 3.0)
.add("v", 25.0, 250.0);

let prior = Theta::sobol_default(&parameters)?;

let error_models = AssayErrorModels::new().add(
"outeq_1",
AssayErrorModel::additive(ErrorPoly::new(0.0, 0.5, 0.0, 0.0), 0.0),
)?;

let problem = EstimationProblem::nonparametric(eq, data, prior, error_models)?;

let _result = problem.fit_with(NonParametricAlgorithm::npag())?;

Ok(())
}
28 changes: 12 additions & 16 deletions examples/bimodal_ke_backend_compare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,17 +202,16 @@ fn run_case<E: pharmsol::Equation + Clone + Send + 'static + EquationMetadataSou
) -> Result<ComparisonResult> {
let data = data::read_pmetrics(DATA_PATH)?;
let fit_started = Instant::now();
let result = EstimationProblem::builder(equation, data)
.parameter(Parameter::bounded("ke", 0.001, 3.0))?
.parameter(Parameter::bounded("v", 25.0, 250.0))?
.method(Npag::new())
.error(
"outeq_1",
AssayErrorModel::additive(ErrorPoly::new(0.0, 0.5, 0.0, 0.0), 0.0),
)?
.cache(true)
.progress(false)
.fit()?;
let parameters = ParameterSpace::bounded()
.add("ke", 0.001, 3.0)
.add("v", 25.0, 250.0);
let prior = Theta::sobol_default(&parameters)?;
let error_models = AssayErrorModels::new().add(
"outeq_1",
AssayErrorModel::additive(ErrorPoly::new(0.0, 0.5, 0.0, 0.0), 0.0),
)?;
let result = EstimationProblem::nonparametric(equation, data, prior, error_models)?
.fit_with(NonParametricAlgorithm::npag())?;
let fit_time = fit_started.elapsed();

summarize_result(label, compile_time, fit_time, &result)
Expand All @@ -222,12 +221,8 @@ fn summarize_result<E: pharmsol::Equation>(
label: &'static str,
compile_time: Duration,
fit_time: Duration,
result: &FitResult<E>,
result: &NonParametricResult<E>,
) -> Result<ComparisonResult> {
result
.as_nonparametric()
.ok_or_else(|| anyhow!("expected nonparametric result for {label}"))?;

Ok(ComparisonResult {
label,
compile_time,
Expand Down Expand Up @@ -318,3 +313,4 @@ fn print_summary(results: &[ComparisonResult]) -> Result<()> {

Ok(())
}

Loading