use approx::assert_relative_eq;
use pharmsol::data::Subject;
use pharmsol::nca::{LambdaZOptions, NCAOptions, NCA};
use pharmsol::SubjectBuilderExt;
fn build_subject_with_dose(times: &[f64], concs: &[f64], dose: f64) -> Subject {
let mut builder = Subject::builder("test").bolus(0.0, dose, 0);
for (&t, &c) in times.iter().zip(concs.iter()) {
builder = builder.observation(t, c, 0);
}
builder.build()
}
#[test]
fn test_clearance_calculation() {
let times = vec![0.0, 1.0, 2.0, 4.0, 8.0, 12.0, 24.0];
let lambda: f64 = 0.1;
let concs: Vec<f64> = times.iter().map(|&t| 100.0 * (-lambda * t).exp()).collect();
let dose = 1000.0;
let subject = build_subject_with_dose(×, &concs, dose);
let options = NCAOptions::default();
let result = subject.nca(&options).expect("NCA should succeed");
if let Some(ref clearance) = result.clearance {
assert!(clearance.cl_f > 0.5 && clearance.cl_f < 2.0);
}
}
#[test]
fn test_volume_distribution() {
let times = vec![0.0, 1.0, 2.0, 4.0, 8.0, 12.0, 24.0];
let lambda: f64 = 0.1;
let concs: Vec<f64> = times.iter().map(|&t| 100.0 * (-lambda * t).exp()).collect();
let dose = 1000.0;
let subject = build_subject_with_dose(×, &concs, dose);
let options = NCAOptions::default();
let result = subject.nca(&options).expect("NCA should succeed");
if let Some(ref clearance) = result.clearance {
assert!(clearance.vz_f > 5.0 && clearance.vz_f < 20.0);
}
}
#[test]
fn test_half_life() {
let times = vec![0.0, 1.0, 2.0, 4.0, 8.0, 12.0, 24.0];
let lambda: f64 = 0.0693; let concs: Vec<f64> = times.iter().map(|&t| 100.0 * (-lambda * t).exp()).collect();
let subject = build_subject_with_dose(×, &concs, 100.0);
let options = NCAOptions::default().with_lambda_z(LambdaZOptions {
min_r_squared: 0.90,
min_span_ratio: 1.0,
..Default::default()
});
let result = subject.nca(&options).expect("NCA should succeed");
if let Some(ref terminal) = result.terminal {
assert_relative_eq!(terminal.half_life, 10.0, epsilon = 1.0);
}
}
#[test]
fn test_cmax_tmax() {
let times = vec![0.0, 0.5, 1.0, 2.0, 4.0, 8.0];
let concs = vec![0.0, 50.0, 80.0, 90.0, 60.0, 30.0];
let subject = build_subject_with_dose(×, &concs, 100.0);
let options = NCAOptions::default();
let result = subject.nca(&options).expect("NCA should succeed");
assert_relative_eq!(result.exposure.cmax, 90.0, epsilon = 0.001);
assert_relative_eq!(result.exposure.tmax, 2.0, epsilon = 0.001);
}
#[test]
fn test_iv_bolus_cmax_at_first_point() {
let times = vec![0.0, 1.0, 2.0, 4.0];
let concs = vec![100.0, 80.0, 60.0, 40.0];
let subject = build_subject_with_dose(×, &concs, 100.0);
let options = NCAOptions::default();
let result = subject.nca(&options).expect("NCA should succeed");
assert_relative_eq!(result.exposure.cmax, 100.0, epsilon = 0.001);
assert_relative_eq!(result.exposure.tmax, 0.0, epsilon = 0.001);
}
#[test]
fn test_clast_tlast() {
let times = vec![0.0, 1.0, 2.0, 4.0, 8.0];
let concs = vec![100.0, 80.0, 60.0, 30.0, 10.0];
let subject = build_subject_with_dose(×, &concs, 100.0);
let options = NCAOptions::default();
let result = subject.nca(&options).expect("NCA should succeed");
assert_relative_eq!(result.exposure.clast, 10.0, epsilon = 0.001);
assert_relative_eq!(result.exposure.tlast, 8.0, epsilon = 0.001);
}
#[test]
fn test_steady_state_parameters() {
let times = vec![0.0, 1.0, 2.0, 4.0, 6.0, 8.0, 12.0];
let concs = vec![50.0, 80.0, 70.0, 55.0, 48.0, 45.0, 50.0];
let tau = 12.0;
let subject = build_subject_with_dose(×, &concs, 100.0);
let options = NCAOptions::default().with_tau(tau);
let result = subject.nca(&options).expect("NCA should succeed");
if let Some(ref ss) = result.steady_state {
assert!(ss.cmin > 40.0 && ss.cmin < 55.0);
assert!(ss.cavg > 50.0 && ss.cavg < 70.0);
assert!(ss.fluctuation > 0.0);
}
}
#[test]
fn test_extrapolation_percent() {
let times = vec![0.0, 1.0, 2.0, 4.0, 8.0, 12.0];
let concs = vec![100.0, 80.0, 65.0, 45.0, 25.0, 15.0];
let subject = build_subject_with_dose(×, &concs, 100.0);
let options = NCAOptions::default();
let result = subject.nca(&options).expect("NCA should succeed");
if let Some(extrap_pct) = result.exposure.auc_pct_extrap_obs {
assert!(extrap_pct < 50.0, "Extrapolation too high: {}", extrap_pct);
}
}
#[test]
fn test_complete_parameter_workflow() {
let times = vec![0.0, 0.5, 1.0, 2.0, 4.0, 8.0, 12.0, 24.0];
let concs = vec![100.0, 91.0, 83.0, 70.0, 49.0, 24.0, 12.0, 1.5];
let dose = 1000.0;
let subject = build_subject_with_dose(×, &concs, dose);
let options = NCAOptions::default();
let result = subject.nca(&options).expect("NCA should succeed");
assert_eq!(result.exposure.cmax, 100.0);
assert_eq!(result.exposure.tmax, 0.0);
assert!(result.exposure.auc_last > 400.0 && result.exposure.auc_last < 600.0);
if let Some(ref terminal) = result.terminal {
assert!(terminal.lambda_z > 0.05 && terminal.lambda_z < 0.20);
assert!(terminal.half_life > 3.0 && terminal.half_life < 15.0);
}
if let Some(ref clearance) = result.clearance {
assert!(clearance.cl_f > 0.0);
assert!(clearance.vz_f > 0.0);
}
println!("Complete parameter set:");
println!(" Cmax: {:.2}", result.exposure.cmax);
println!(" Tmax: {:.2}", result.exposure.tmax);
println!(" AUClast: {:.2}", result.exposure.auc_last);
if let Some(auc_inf) = result.exposure.auc_inf_obs {
println!(" AUCinf: {:.2}", auc_inf);
}
if let Some(ref terminal) = result.terminal {
println!(" Lambda_z: {:.4}", terminal.lambda_z);
println!(" Half-life: {:.2}", terminal.half_life);
}
}