Skip to main content

pmcore/algorithms/nonparametric/
mod.rs

1//! Non-parametric algorithm implementations
2//!
3//! This module contains the trait definition and implementations for non-parametric
4//! population pharmacokinetic algorithms. These algorithms estimate the population
5//! distribution as a discrete set of support points with associated probability weights.
6//!
7//! # Available Algorithms
8//!
9//! - [`NPAG`](npag): Non-Parametric Adaptive Grid
10//! - [`NPOD`](npod): Non-Parametric Optimal Design
11//! - [`NPMAP`](npmap): Maximum a posteriori reweighting
12//!
13//! # Algorithm Selection
14//!
15//! Use the [`NonParametricAlgorithm`] enum to select and configure an algorithm. Each
16//! variant wraps its algorithm-specific configuration struct (e.g. [`NpagConfig`]). The
17//! internal execution state used while fitting implements the [`NonParametricRunner`]
18//! trait, which defines the common interface for initialization, estimation, condensation,
19//! expansion, and convergence evaluation.
20
21// Shared error-model factor optimization
22pub mod error_optim;
23
24// Algorithm implementations
25pub mod ncnpag;
26pub mod npag;
27pub mod npmap;
28pub mod npod;
29
30// Incremental, observable fitting (stepping handle + per-cycle observers).
31pub mod controller;
32
33// Re-export algorithm structs
34pub use ncnpag::NCNPAG;
35pub use npag::NPAG;
36pub use npmap::NPMAP;
37pub use npod::NPOD;
38
39// Re-export per-algorithm configuration structs
40pub use error_optim::ErrorOptimConfig;
41pub use ncnpag::NcnpagConfig;
42pub use npag::NpagConfig;
43pub use npmap::NpmapConfig;
44pub use npod::NpodConfig;
45
46// Re-export the incremental fitting API
47pub use controller::{CycleFlow, FitController, FitObserver};
48
49use crate::algorithms::{Algorithm, NonParametricRunner};
50use crate::estimation::nonparametric::NonParametricResult;
51use crate::estimation::{EstimationProblem, NonParametric};
52use anyhow::Result;
53use pharmsol::prelude::simulator::Equation;
54
55/// The non-parametric algorithms supported by PMcore.
56///
57/// Use the constructors to select an algorithm with its default configuration:
58///
59/// ```no_run
60/// use pmcore::prelude::*;
61///
62/// // Default NPAG configuration.
63/// let algorithm = NonParametricAlgorithm::npag();
64/// ```
65///
66/// To customize an algorithm, build its configuration struct (which exposes only the
67/// setters valid for that algorithm) and pass it directly to
68/// [`fit_with`](crate::estimation::EstimationProblem::fit_with):
69///
70/// ```no_run
71/// use pmcore::prelude::*;
72///
73/// // NPAG with a tighter convergence criterion and a cycle cap.
74/// let config = NpagConfig::new().eps(0.1).max_cycles(500);
75/// // `problem.fit_with(config)` accepts the config directly.
76/// ```
77///
78/// Each configuration type ([`NpagConfig`], [`NpodConfig`], [`NpmapConfig`]) implements
79/// [`Algorithm`] by delegating to the matching enum variant, so configs can be passed to
80/// `fit_with` without converting them first.
81#[derive(Debug, Clone)]
82pub enum NonParametricAlgorithm {
83    /// Non-Parametric Adaptive Grid.
84    Npag(NpagConfig),
85    /// Non-Parametric Optimal Design.
86    Npod(NpodConfig),
87    /// Non-parametric maximum a posteriori (posterior probability reweighting).
88    Npmap(NpmapConfig),
89    /// Non-collapsing NPAG (single-pass Bayesian reweighting of fixed support points).
90    Ncnpag(NcnpagConfig),
91}
92
93impl Default for NonParametricAlgorithm {
94    fn default() -> Self {
95        Self::npag()
96    }
97}
98
99impl From<NpagConfig> for NonParametricAlgorithm {
100    fn from(config: NpagConfig) -> Self {
101        Self::Npag(config)
102    }
103}
104
105impl From<NpodConfig> for NonParametricAlgorithm {
106    fn from(config: NpodConfig) -> Self {
107        Self::Npod(config)
108    }
109}
110
111impl From<NpmapConfig> for NonParametricAlgorithm {
112    fn from(config: NpmapConfig) -> Self {
113        Self::Npmap(config)
114    }
115}
116
117impl From<NcnpagConfig> for NonParametricAlgorithm {
118    fn from(config: NcnpagConfig) -> Self {
119        Self::Ncnpag(config)
120    }
121}
122
123impl NonParametricAlgorithm {
124    /// The Non-Parametric Adaptive Grid (NPAG) algorithm with its default configuration.
125    pub fn npag() -> Self {
126        Self::Npag(NpagConfig::default())
127    }
128
129    /// The Non-Parametric Optimal Design (NPOD) algorithm with its default configuration.
130    pub fn npod() -> Self {
131        Self::Npod(NpodConfig::default())
132    }
133
134    /// The non-parametric maximum a posteriori (NPMAP) algorithm with its default
135    /// configuration.
136    pub fn npmap() -> Self {
137        Self::Npmap(NpmapConfig::default())
138    }
139
140    /// The non-collapsing NPAG (NCNPAG) algorithm with its default configuration.
141    pub fn ncnpag() -> Self {
142        Self::Ncnpag(NcnpagConfig::default())
143    }
144
145    /// Build the internal, mutable execution state (runner) for this algorithm.
146    ///
147    /// Both [`fit`](Algorithm::fit) and the stepping
148    /// [`FitController`](crate::algorithms::nonparametric::controller::FitController)
149    /// build on this primitive.
150    pub(crate) fn into_runner<E: Equation + Send + 'static>(
151        self,
152        problem: EstimationProblem<E, NonParametric>,
153    ) -> Result<Box<dyn NonParametricRunner<E>>> {
154        // `problem.prior` is the prior `Theta` (which also carries the parameter
155        // space) and `problem.error_models` is strictly `AssayErrorModels`.
156        let runner: Box<dyn NonParametricRunner<E>> = match self {
157            Self::Npag(config) => Box::new(NPAG::from_parts(
158                problem.model.equation,
159                problem.data,
160                problem.error_models,
161                problem.prior,
162                config,
163            )?),
164            Self::Npod(config) => Box::new(NPOD::from_parts(
165                problem.model.equation,
166                problem.data,
167                problem.error_models,
168                problem.prior,
169                config,
170            )?),
171            Self::Npmap(config) => Box::new(NPMAP::from_parts(
172                problem.model.equation,
173                problem.data,
174                problem.error_models,
175                problem.prior,
176                config,
177            )?),
178            Self::Ncnpag(config) => Box::new(NCNPAG::from_parts(
179                problem.model.equation,
180                problem.data,
181                problem.error_models,
182                problem.prior,
183                config,
184            )?),
185        };
186        Ok(runner)
187    }
188}
189
190impl<E: Equation + Send + 'static> Algorithm<E, NonParametric> for NonParametricAlgorithm {
191    type Output = NonParametricResult<E>;
192
193    fn fit(self, problem: EstimationProblem<E, NonParametric>) -> Result<Self::Output> {
194        let mut runner = self.into_runner(problem)?;
195        runner.fit()
196    }
197}
198
199// Each configuration struct delegates to its matching `NonParametricAlgorithm` variant so it
200// can be passed directly to `fit_with`. This keeps the variant-specific setters on the config
201// types (compile-time checked) while the enum remains the single source of fitting logic.
202impl<E: Equation + Send + 'static> Algorithm<E, NonParametric> for NpagConfig {
203    type Output = NonParametricResult<E>;
204
205    fn fit(self, problem: EstimationProblem<E, NonParametric>) -> Result<Self::Output> {
206        NonParametricAlgorithm::from(self).fit(problem)
207    }
208}
209
210impl<E: Equation + Send + 'static> Algorithm<E, NonParametric> for NpodConfig {
211    type Output = NonParametricResult<E>;
212
213    fn fit(self, problem: EstimationProblem<E, NonParametric>) -> Result<Self::Output> {
214        NonParametricAlgorithm::from(self).fit(problem)
215    }
216}
217
218impl<E: Equation + Send + 'static> Algorithm<E, NonParametric> for NpmapConfig {
219    type Output = NonParametricResult<E>;
220
221    fn fit(self, problem: EstimationProblem<E, NonParametric>) -> Result<Self::Output> {
222        NonParametricAlgorithm::from(self).fit(problem)
223    }
224}
225
226impl<E: Equation + Send + 'static> Algorithm<E, NonParametric> for NcnpagConfig {
227    type Output = NonParametricResult<E>;
228
229    fn fit(self, problem: EstimationProblem<E, NonParametric>) -> Result<Self::Output> {
230        NonParametricAlgorithm::from(self).fit(problem)
231    }
232}