Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
124 changes: 123 additions & 1 deletion pharmsol-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3208,6 +3208,45 @@ fn expand_sde_out(
// Proc macros
// ---------------------------------------------------------------------------

/// Define an ODE (ordinary differential equation) model.
///
/// This is the primary entry point for building pharmacometric ODE models.
/// The macro generates and validates an `ODE` model and automatically generates its metadata
/// (parameter names, state labels, output labels, route declarations).
Comment thread
mhovd marked this conversation as resolved.
///
/// # Fields
///
/// | Field | Required | Description |
/// |-------|----------|-------------|
/// | `name` | yes | Model name (`"my_model"`) |
/// | `params` | yes | Parameter identifiers `[ka, ke, v]` |
/// | `covariates` | no | Covariate identifiers `[wt, age]` |
/// | `states` | yes | State identifiers `[gut, central]` |
/// | `outputs` | yes | Output identifiers `[cp]` |
/// | `routes` | no | Route declarations `[bolus(oral) -> gut, infusion(iv) -> central]` |
/// | `diffeq` | yes | Closure `\|x, p, t, dx, cov\| { … }` writing derivatives into `dx` |
/// | `lag` | no | Closure returning route‑specific lag times via `lag! { route => expr }` |
/// | `fa` | no | Closure returning bioavailability fractions |
/// | `init` | no | Closure setting initial state values |
/// | `out` | yes | Closure `\|x, p, t, cov, y\| { … }` mapping states to outputs |
///
/// # Example
///
/// ```ignore
/// let model = ode! {
/// name: "one_cmt_iv",
/// params: [ke, v],
/// states: [central],
/// outputs: [cp],
/// routes: [infusion(iv) -> central],
/// diffeq: |x, _p, _t, dx, _cov| {
/// dx[central] = -ke * x[central];
/// },
/// out: |x, _p, _t, _cov, y| {
/// y[cp] = x[central] / v;
/// },
/// };
/// ```
#[proc_macro]
pub fn ode(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as OdeInput);
Expand Down Expand Up @@ -3365,6 +3404,45 @@ pub fn ode(input: TokenStream) -> TokenStream {
.into()
}

/// Define an analytical (closed‑form) PK model.
///
/// Builds a model that uses a built‑in analytical solution. The macro validates that the declared parameters
/// match the chosen analytical solution's requirements and generates an `Analytical` value
/// with full metadata.
Comment thread
mhovd marked this conversation as resolved.
///
/// # Fields
///
/// | Field | Required | Description |
/// |-------|----------|-------------|
/// | `name` | yes | Model name |
/// | `params` | yes | Parameter identifiers |
/// | `derived` | no | Derived parameter identifiers (computed in `derive`) |
/// | `covariates` | no | Covariate identifiers |
/// | `states` | yes | State identifiers |
/// | `outputs` | yes | Output identifiers |
/// | `routes` | no | Route declarations |
/// | `structure` | yes | Built‑in kernel name, e.g. `one_compartment` or `one_compartment_with_absorption` |
/// | `derive` | no | Closure `\|t\| { … }` computing derived parameters from primaries and covariates |
/// | `lag` | no | Lag‑time closure |
/// | `fa` | no | Bioavailability closure |
/// | `init` | no | Initial‑state closure |
/// | `out` | yes | Output mapping closure |
///
/// # Example
///
/// ```ignore
/// let model = analytical! {
/// name: "one_cmt_oral",
/// params: [ka, ke, v],
/// states: [gut, central],
/// outputs: [cp],
/// routes: [bolus(oral) -> gut],
/// structure: one_compartment_with_absorption,
/// out: |x, _t, y| {
/// y[cp] = x[central] / v;
/// },
/// };
/// ```
#[proc_macro]
pub fn analytical(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as AnalyticalInput);
Expand Down Expand Up @@ -3506,7 +3584,6 @@ pub fn analytical(input: TokenStream) -> TokenStream {
let nstates = input.states.len();
let ndrugs = dense_index_len(&route_bindings);
let nout = input.outputs.len();

let name = &input.name;
let params = &input.params;
let covariates = &input.covariates;
Expand Down Expand Up @@ -3550,6 +3627,51 @@ pub fn analytical(input: TokenStream) -> TokenStream {
.into()
}

/// Define an SDE (stochastic differential equation) model.
///
/// Builds a particle‑based stochastic model with a drift term, a diffusion
/// term, and a configurable number of particles. The macro generates an `SDE`
/// value with full metadata.
Comment thread
mhovd marked this conversation as resolved.
///
/// # Fields
///
/// | Field | Required | Description |
/// |-------|----------|-------------|
/// | `name` | yes | Model name |
/// | `params` | yes | Parameter identifiers |
/// | `covariates` | no | Covariate identifiers |
/// | `states` | yes | State identifiers |
/// | `outputs` | yes | Output identifiers |
/// | `particles` | yes | Number of particles for the simulation |
/// | `routes` | no | Route declarations |
/// | `drift` | yes | Closure `\|x, p, t, dx, cov\| { … }` for the deterministic drift |
/// | `diffusion` | yes | Closure `\|p, sigma\| { … }` setting per‑state diffusion coefficients |
/// | `lag` | no | Lag‑time closure |
/// | `fa` | no | Bioavailability closure |
/// | `init` | no | Initial‑state closure |
/// | `out` | yes | Output mapping closure |
///
/// # Example
///
/// ```ignore
/// let model = sde! {
/// name: "one_cmt_sde",
/// params: [ke, sigma_ke, v],
/// states: [central],
/// outputs: [cp],
/// particles: 16,
/// routes: [infusion(iv) -> central],
/// drift: |x, _p, _t, dx, _cov| {
/// dx[central] = -ke * x[central];
/// },
/// diffusion: |_p, sigma| {
/// sigma[central] = sigma_ke;
/// },
/// out: |x, _p, _t, _cov, y| {
/// y[cp] = x[central] / v;
/// },
/// };
/// ```
#[proc_macro]
pub fn sde(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as SdeInput);
Expand Down
6 changes: 6 additions & 0 deletions src/optimize/effect.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
//! Maximum-effect (`E2`) optimization for dual-site pharmacodynamic models.
//!
//! The central entry point is [`get_e2`], which computes the maximum achievable
//! effect for a model with two binding sites via Nelder‑Mead optimization in
//! log‑space.

use argmin::{
core::{CostFunction, Executor, TerminationReason, TerminationStatus},
solver::neldermead::NelderMead,
Expand Down
9 changes: 9 additions & 0 deletions src/optimize/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
//! Optimizer-oriented helpers for pharmacometric workflows.
//!
//! This module provides optimization utilities built on [`argmin`]:
//!
//! - [`effect`] — Find the maximum effect (`E2`) for dual-site PD models
//! via Nelder‑Mead optimization in log‑space.
//! - [`parameters`] — Nelder‑Mead parameter refinement for an [`Equation`]
//! against a [`Data`] set and [`AssayErrorModels`].

pub mod effect;
pub mod parameters;
17 changes: 17 additions & 0 deletions src/optimize/parameters.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
//! Nelder‑Mead parameter refinement for pharmacometric models.
//!
//! This module provides a [`ParameterOptimizer`] that refines a single parameter
//! Given an [`Equation`], observed [`Data`], and [`AssayErrorModels`] via
//! Nelder‑Mead optimization in log‑space. The optimizer finds the parameter vector
//! that minimizes the negative log-likelihood of the model predictions against the data,
//! as measured by the provided error models.
Comment on lines +1 to +7

use argmin::{
core::{CostFunction, Error, Executor},
solver::neldermead::NelderMead,
Expand All @@ -7,6 +15,7 @@ use ndarray::{Array1, Axis};

use crate::{prelude::simulator::log_likelihood_matrix, AssayErrorModels, Data, Equation};

/// Optimizer that refines a single parameter vector against observed data.
pub struct ParameterOptimizer<'a, E: Equation> {
equation: &'a E,
data: &'a Data,
Expand Down Expand Up @@ -44,6 +53,12 @@ impl<E: Equation> CostFunction for ParameterOptimizer<'_, E> {
}

impl<'a, E: Equation> ParameterOptimizer<'a, E> {
/// Create a new optimizer.
///
/// * `equation` — the model to evaluate.
/// * `data` — observed subject data.
/// * `sig` — assay error models per output.
/// * `pyl` — reference (target) likelihood vector.
pub fn new(
equation: &'a E,
data: &'a Data,
Expand All @@ -58,6 +73,8 @@ impl<'a, E: Equation> ParameterOptimizer<'a, E> {
}
}

/// Optimize the parameters to minimize the negative log-likelihood against the data.

pub fn optimize_point(self, parameters: Array1<f64>) -> Result<Array1<f64>, Error> {
let simplex = create_initial_simplex(&parameters.to_vec());
let solver: NelderMead<Vec<f64>, f64> = NelderMead::new(simplex).with_sd_tolerance(1e-2)?;
Expand Down
Loading