-
Notifications
You must be signed in to change notification settings - Fork 3
feat: Chain fit methods using result #284
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Siel
wants to merge
3
commits into
unified-platform-structure
Choose a base branch
from
feature/chain_bootstrap_iov
base: unified-platform-structure
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -313,4 +313,3 @@ fn print_summary(results: &[ComparisonResult]) -> Result<()> { | |
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| //! Model complexity bootstrapping: expand a 1-compartment posterior into a | ||
| //! prior for a 2-compartment model using `bootstrap_theta()`. | ||
| //! | ||
| //! Stage 1 fits (ke, v) with NPAG. Stage 2 expands the posterior into a | ||
| //! prior over (ke, v, kcp, kpc) and fits the 2-compartment model. | ||
| //! | ||
| //! Uses inline synthetic data to keep the example self-contained. | ||
|
|
||
| use pharmsol::SubjectBuilderExt; | ||
| use pmcore::prelude::*; | ||
|
|
||
| fn main() -> Result<()> { | ||
| // ── Synthetic data ── | ||
| let subject = Subject::builder("1") | ||
| .bolus(0.0, 100.0, 1) | ||
| .observation(1.0, 8.0, 1) | ||
| .observation(2.0, 5.0, 1) | ||
| .observation(4.0, 2.0, 1) | ||
| .build(); | ||
| let data = Data::new(vec![subject]); | ||
|
|
||
| // ── Stage 1: 1-compartment ODE ── | ||
|
|
||
| let ode_1cmt = ode! { | ||
| name: "one_cmt_bootstrap", | ||
| params: [ke, v], | ||
| states: [central], | ||
| outputs: [outeq_1], | ||
| routes: [bolus(input_1) -> central], | ||
| diffeq: |x, p, _t, dx, _cov| { | ||
| fetch_params!(p, ke); | ||
| dx[0] = -ke * x[0]; | ||
| }, | ||
| out: |x, p, _t, _cov, y| { | ||
| fetch_params!(p, v); | ||
| y[0] = x[0] / v; | ||
| }, | ||
| }; | ||
|
|
||
| let params_1cmt = ParameterSpace::bounded() | ||
| .add("ke", 0.001, 3.0) | ||
| .add("v", 5.0, 50.0); | ||
| let prior = Theta::sobol(¶ms_1cmt, 100)?; | ||
| let error_models = AssayErrorModels::new().add( | ||
| "outeq_1", | ||
| AssayErrorModel::additive(ErrorPoly::new(0.0, 0.5, 0.0, 0.0), 0.0), | ||
| )?; | ||
|
|
||
| println!("Stage 1: Fitting 1-cmt model..."); | ||
| let r1 = EstimationProblem::nonparametric(ode_1cmt, data.clone(), prior, error_models.clone())? | ||
| .fit_with(NonParametricAlgorithm::npag())?; | ||
|
|
||
| println!( | ||
| " {} support points, OBJF = {:.2}", | ||
| r1.get_theta().nspp(), | ||
| r1.objf(), | ||
| ); | ||
|
|
||
| // ── Stage 2: bootstrap into 2-compartment prior ── | ||
|
|
||
| let ode_2cmt = ode! { | ||
| name: "two_cmt_bootstrap", | ||
| params: [ke, v, kcp, kpc], | ||
| states: [central, peripheral], | ||
| outputs: [outeq_1], | ||
| routes: [bolus(input_1) -> central], | ||
| diffeq: |x, p, _t, dx, _cov| { | ||
| fetch_params!(p, ke, _v, kcp, kpc); | ||
| dx[0] = -ke * x[0] - kcp * x[0] + kpc * x[1]; | ||
| dx[1] = -kpc * x[1] + kcp * x[0]; | ||
| }, | ||
| out: |x, p, _t, _cov, y| { | ||
| fetch_params!(p, _ke, v); | ||
| y[0] = x[0] / v; | ||
| }, | ||
| }; | ||
|
|
||
| let params_2cmt = ParameterSpace::bounded() | ||
| .add("ke", 0.001, 3.0) | ||
| .add("v", 5.0, 50.0) | ||
| .add("kcp", 0.01, 5.0) | ||
| .add("kpc", 0.01, 5.0); | ||
|
|
||
| // Expand posterior: common params keep their values, new ones are Sobol-sampled | ||
| let prior_2cmt = bootstrap_theta(&r1, &ode_2cmt, ¶ms_2cmt, 500)?; | ||
|
|
||
| println!( | ||
| "Prior expanded: {} support points over {:?}", | ||
| prior_2cmt.nspp(), | ||
| prior_2cmt.parameters().names(), | ||
| ); | ||
|
|
||
| println!("Stage 2: Fitting 2-cmt model..."); | ||
| let r2 = EstimationProblem::nonparametric(ode_2cmt, data, prior_2cmt, error_models)? | ||
| .fit_with(NonParametricAlgorithm::npag())?; | ||
|
|
||
| println!( | ||
| " {} support points, OBJF = {:.2}", | ||
| r2.get_theta().nspp(), | ||
| r2.objf(), | ||
| ); | ||
|
|
||
| Ok(()) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| //! Algorithm chaining: NPAG(10) → NPOD(5) on a simple 1-compartment IV bolus model. | ||
| //! | ||
| //! Demonstrates `NonParametricResult::chain()`. | ||
| //! Uses inline synthetic data to keep the example self-contained. | ||
|
|
||
| use pharmsol::SubjectBuilderExt; | ||
| use pmcore::prelude::*; | ||
|
|
||
| fn main() -> Result<()> { | ||
| let eq = ode! { | ||
| name: "chain_example", | ||
| params: [ke, v], | ||
| states: [central], | ||
| outputs: [outeq_1], | ||
| routes: [bolus(input_1) -> central], | ||
| diffeq: |x, p, _t, dx, _cov| { | ||
| fetch_params!(p, ke); | ||
| dx[0] = -ke * x[0]; | ||
| }, | ||
| out: |x, p, _t, _cov, y| { | ||
| fetch_params!(p, v); | ||
| y[0] = x[0] / v; | ||
| }, | ||
| }; | ||
|
|
||
| // Small synthetic dataset: 2 subjects, 3 obs each | ||
| let subject1 = Subject::builder("1") | ||
| .bolus(0.0, 100.0, 1) | ||
| .observation(1.0, 8.0, 1) | ||
| .observation(2.0, 5.0, 1) | ||
| .observation(4.0, 2.0, 1) | ||
| .build(); | ||
|
|
||
| let subject2 = Subject::builder("2") | ||
| .bolus(0.0, 80.0, 1) | ||
| .observation(1.0, 6.0, 1) | ||
| .observation(2.0, 4.0, 1) | ||
| .observation(4.0, 1.5, 1) | ||
| .build(); | ||
|
|
||
| let data = Data::new(vec![subject1, subject2]); | ||
|
|
||
| let parameters = ParameterSpace::bounded() | ||
| .add("ke", 0.001, 3.0) | ||
| .add("v", 5.0, 50.0); | ||
| let prior = Theta::sobol(¶meters, 100)?; | ||
| let error_models = AssayErrorModels::new().add( | ||
| "outeq_1", | ||
| AssayErrorModel::additive(ErrorPoly::new(0.0, 0.5, 0.0, 0.0), 0.0), | ||
| )?; | ||
|
|
||
| // Chain: NPAG runs first, then NPOD continues from its result | ||
| let result = EstimationProblem::nonparametric(eq, data, prior, error_models)? | ||
| .fit_with(NpagConfig::new().max_cycles(10))? | ||
| .chain(NpodConfig::new().max_cycles(5))?; | ||
|
|
||
| println!( | ||
| "Chained NPAG→NPOD: OBJF = {:.2}, {} support points, {} total cycles", | ||
| result.objf(), | ||
| result.get_theta().nspp(), | ||
| result.cycles(), | ||
| ); | ||
|
|
||
| Ok(()) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -111,4 +111,3 @@ fn main() -> Result<()> { | |
| .fit_with(NonParametricAlgorithm::npag())?; | ||
| Ok(()) | ||
| } | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,4 +39,3 @@ fn main() -> Result<()> { | |
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| //! SDE Inter-Occasion Variability (IOV) analysis. | ||
| //! | ||
| //! Stage 1: NPAG fit with ODE to find support points. | ||
| //! Stage 2: Optimize SDE diffusion parameter (ske) per support point. | ||
| //! | ||
| //! Uses small synthetic data and low particle count (50) to keep the example fast. | ||
|
|
||
| use pharmsol::SubjectBuilderExt; | ||
| use pmcore::iov::DiffusionConfig; | ||
| use pmcore::prelude::*; | ||
|
|
||
| fn main() -> Result<()> { | ||
| // ── Synthetic data ── | ||
| let subject = Subject::builder("1") | ||
| .bolus(0.0, 100.0, 1) | ||
| .observation(1.0, 10.0, 1) | ||
| .observation(2.0, 6.0, 1) | ||
| .observation(4.0, 2.5, 1) | ||
| .build(); | ||
| let data = Data::new(vec![subject]); | ||
|
|
||
| // ── Stage 1: ODE fit ── | ||
|
|
||
| let ode = ode! { | ||
| name: "iov_stage1_ode", | ||
| params: [ke, v], | ||
| states: [central], | ||
| outputs: [outeq_1], | ||
| routes: [bolus(input_1) -> central], | ||
| diffeq: |x, p, _t, dx, _cov| { | ||
| fetch_params!(p, ke); | ||
| dx[0] = -ke * x[0]; | ||
| }, | ||
| out: |x, p, _t, _cov, y| { | ||
| fetch_params!(p, v); | ||
| y[0] = x[0] / v; | ||
| }, | ||
| }; | ||
|
|
||
| let parameters = ParameterSpace::bounded() | ||
| .add("ke", 0.001, 2.0) | ||
| .add("v", 5.0, 50.0); | ||
| let prior = Theta::sobol(¶meters, 50)?; | ||
| let error_models = AssayErrorModels::new().add( | ||
| "outeq_1", | ||
| AssayErrorModel::additive(ErrorPoly::new(0.0, 0.5, 0.0, 0.0), 0.0), | ||
| )?; | ||
|
|
||
| println!("Stage 1: Fitting ODE with NPAG..."); | ||
| let r_ode = EstimationProblem::nonparametric(ode, data.clone(), prior, error_models.clone())? | ||
| .fit_with(NonParametricAlgorithm::npag())?; | ||
|
|
||
| let n_spp = r_ode.get_theta().nspp(); | ||
| println!(" {} support points, OBJF = {:.2}", n_spp, r_ode.objf()); | ||
|
|
||
| // ── Stage 2: SDE IOV ── | ||
|
|
||
| let sde = sde! { | ||
| name: "iov_stage2_sde", | ||
| params: [ke, v, ske], | ||
| states: [central, ke0], | ||
| outputs: [outeq_1], | ||
| particles: 50, | ||
| routes: [bolus(input_1) -> central], | ||
| drift: |x, p, _t, dx, _cov| { | ||
| fetch_params!(p, ke); | ||
| dx[ke0] = -x[ke0] + ke; | ||
| dx[central] = -x[ke0] * x[central]; | ||
| }, | ||
| diffusion: |p, sigma| { | ||
| fetch_params!(p, _ke, _v, ske); | ||
| sigma[ke0] = ske; | ||
| }, | ||
| init: |p, _t, _cov, x| { | ||
| fetch_params!(p, ke); | ||
| x[ke0] = ke; | ||
| }, | ||
| out: |x, p, _t, _cov, y| { | ||
| fetch_params!(p, _ke, v); | ||
| y[0] = x[central] / v; | ||
| }, | ||
| }; | ||
|
|
||
| let mut joint = r_ode | ||
| .get_theta() | ||
| .with_added_parameter("ske", 1e-6, 1.0, 0.01)?; | ||
|
|
||
| println!( | ||
| "Stage 2: Optimizing sigma for {} support points...", | ||
| joint.nspp() | ||
| ); | ||
|
|
||
| let diff = sde.optimize_diffusion( | ||
| r_ode.data(), | ||
| &mut joint, | ||
| &["ske".to_string()], | ||
| r_ode.error_models(), | ||
| DiffusionConfig { | ||
| max_iter: 10, | ||
| ..DiffusionConfig::default() | ||
| }, | ||
| )?; | ||
|
|
||
| let n_converged = diff.per_point_converged.iter().filter(|&&c| c).count(); | ||
| let mean_ll: f64 = | ||
| diff.per_point_likelihood.iter().sum::<f64>() / diff.per_point_likelihood.len() as f64; | ||
|
|
||
| println!( | ||
| " Converged: {}/{} points, mean log-likelihood: {:.4}", | ||
| n_converged, | ||
| diff.per_point_converged.len(), | ||
| mean_ll, | ||
| ); | ||
|
|
||
| Ok(()) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,4 +55,3 @@ fn main() -> Result<()> { | |
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -77,4 +77,3 @@ fn main() -> Result<()> { | |
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,4 +39,3 @@ fn main() -> Result<()> { | |
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,4 +30,3 @@ fn main() -> Result<()> { | |
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,4 +37,3 @@ fn main() -> Result<()> { | |
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -65,4 +65,3 @@ fn main() -> Result<()> { | |
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great!