diff --git a/src/data/builder.rs b/src/data/builder.rs index 2a8a813..299d627 100644 --- a/src/data/builder.rs +++ b/src/data/builder.rs @@ -66,7 +66,7 @@ impl SubjectBuilder { /// /// * `time` - Time of the bolus dose /// * `amount` - Amount of drug administered - /// * `input` - The compartment number (zero-indexed) receiving the dose + /// * `input` - The compartment number receiving the dose pub fn bolus(self, time: f64, amount: f64, input: usize) -> Self { let bolus = Bolus::new(time, amount, input, self.current_occasion.index()); let event = Event::Bolus(bolus); @@ -79,7 +79,7 @@ impl SubjectBuilder { /// /// * `time` - Start time of the infusion /// * `amount` - Total amount of drug to be administered - /// * `input` - The compartment number (zero-indexed) receiving the dose + /// * `input` - The compartment number receiving the dose /// * `duration` - Duration of the infusion in time units pub fn infusion(self, time: f64, amount: f64, input: usize, duration: f64) -> Self { let infusion = Infusion::new(time, amount, input, duration, self.current_occasion.index()); @@ -93,8 +93,7 @@ impl SubjectBuilder { /// /// * `time` - Time of the observation /// * `value` - Observed value (e.g., drug concentration) - /// * `outeq` - Output equation number (zero-indexed) corresponding to this observation - /// * `errorpoly` - Error polynomial coefficients (c0, c1, c2, c3) + /// * `outeq` - Output equation number corresponding to this observation pub fn observation(self, time: f64, value: f64, outeq: usize) -> Self { let observation = Observation::new( time, diff --git a/src/data/event.rs b/src/data/event.rs index 75d66cd..1b7724f 100644 --- a/src/data/event.rs +++ b/src/data/event.rs @@ -87,7 +87,7 @@ impl Bolus { /// /// * `time` - Time of the bolus dose /// * `amount` - Amount of drug administered - /// * `input` - The compartment number (zero-indexed) receiving the dose + /// * `input` - The compartment number receiving the dose pub fn new(time: f64, amount: f64, input: usize, occasion: usize) -> Self { Bolus { time, @@ -102,7 +102,7 @@ impl Bolus { self.amount } - /// Get the compartment number (zero-indexed) that receives the bolus + /// Get the compartment number that receives the bolus pub fn input(&self) -> usize { self.input } @@ -117,7 +117,7 @@ impl Bolus { self.amount = amount; } - /// Set the compartment number (zero-indexed) that receives the bolus + /// Set the compartment number that receives the bolus pub fn set_input(&mut self, input: usize) { self.input = input; } @@ -132,7 +132,7 @@ impl Bolus { &mut self.amount } - /// Get a mutable reference to the compartment number that receives the bolus + /// Get a mutable reference to the compartment number (1-indexed) that receives the bolus pub fn mut_input(&mut self) -> &mut usize { &mut self.input } @@ -171,7 +171,7 @@ impl Infusion { /// /// * `time` - Start time of the infusion /// * `amount` - Total amount of drug to be administered - /// * `input` - The compartment number (zero-indexed) receiving the dose + /// * `input` - The compartment number receiving the dose /// * `duration` - Duration of the infusion in time units pub fn new(time: f64, amount: f64, input: usize, duration: f64, occasion: usize) -> Self { Infusion { @@ -188,7 +188,7 @@ impl Infusion { self.amount } - /// Get the compartment number (zero-indexed) that receives the infusion + /// Get the compartment number that receives the infusion pub fn input(&self) -> usize { self.input } @@ -210,7 +210,7 @@ impl Infusion { self.amount = amount; } - /// Set the compartment number (zero-indexed) that receives the infusion + /// Set the compartment number that receives the infusion pub fn set_input(&mut self, input: usize) { self.input = input; } @@ -230,7 +230,7 @@ impl Infusion { &mut self.amount } - /// Set the compartment number (zero-indexed) that receives the infusion + /// Get a mutable reference to the compartment number (1-indexed) that receives the infusion pub fn mut_input(&mut self) -> &mut usize { &mut self.input } @@ -284,9 +284,10 @@ impl Observation { /// /// * `time` - Time of the observation /// * `value` - Observed value (e.g., drug concentration) - /// * `outeq` - Output equation number (zero-indexed) corresponding to this observation + /// * `outeq` - Output equation number corresponding to this observation /// * `errorpoly` - Optional error polynomial coefficients (c0, c1, c2, c3) - /// * `ignore` - Whether to ignore this observation in calculations + /// * `occasion` - Occasion index + /// * `censoring` - Censoring type for this observation pub(crate) fn new( time: f64, value: Option, @@ -315,7 +316,7 @@ impl Observation { self.value } - /// Get the output equation number (zero-indexed) corresponding to this observation + /// Get the output equation number corresponding to this observation pub fn outeq(&self) -> usize { self.outeq } @@ -337,7 +338,7 @@ impl Observation { self.value = value; } - /// Set the output equation number (zero-indexed) corresponding to this observation + /// Set the output equation number corresponding to this observation pub fn set_outeq(&mut self, outeq: usize) { self.outeq = outeq; } diff --git a/src/data/parser/normalized.rs b/src/data/parser/normalized.rs index 72ba1a1..c502d8d 100644 --- a/src/data/parser/normalized.rs +++ b/src/data/parser/normalized.rs @@ -47,7 +47,7 @@ use std::collections::HashMap; /// # Fields /// /// All fields use Pmetrics conventions: -/// - `input` and `outeq` are **1-indexed** (will be converted to 0-indexed internally) +/// - `input` and `outeq` are **1-indexed** (kept as-is, user must size arrays accordingly) /// - `evid`: 0=observation, 1=dose, 4=reset/new occasion /// - `addl`: positive=forward in time, negative=backward in time /// @@ -92,11 +92,11 @@ pub struct NormalizedRow { pub addl: Option, /// Interdose interval for ADDL pub ii: Option, - /// Input compartment (1-indexed in Pmetrics convention) + /// Input compartment pub input: Option, /// Observed value (for EVID=0) pub out: Option, - /// Output equation number (1-indexed) + /// Output equation number pub outeq: Option, /// Censoring indicator pub cens: Option, @@ -201,8 +201,7 @@ impl NormalizedRow { .ok_or_else(|| PmetricsError::MissingObservationOuteq { id: self.id.clone(), time: self.time, - })? - .saturating_sub(1), // Convert 1-indexed to 0-indexed + })?, // Keep 1-indexed as provided by Pmetrics self.get_errorpoly(), 0, // occasion set later self.cens.unwrap_or(Censor::None), @@ -210,13 +209,10 @@ impl NormalizedRow { } 1 | 4 => { // Dosing event (1) or reset with dose (4) - let input_0indexed = self - .input - .ok_or_else(|| PmetricsError::MissingBolusInput { - id: self.id.clone(), - time: self.time, - })? - .saturating_sub(1); // Convert 1-indexed to 0-indexed + let input = self.input.ok_or_else(|| PmetricsError::MissingBolusInput { + id: self.id.clone(), + time: self.time, + })?; // Keep 1-indexed as provided by Pmetrics let event = if self.dur.unwrap_or(0.0) > 0.0 { // Infusion @@ -227,7 +223,7 @@ impl NormalizedRow { id: self.id.clone(), time: self.time, })?, - input_0indexed, + input, self.dur.ok_or_else(|| PmetricsError::MissingInfusionDur { id: self.id.clone(), time: self.time, @@ -242,7 +238,7 @@ impl NormalizedRow { id: self.id.clone(), time: self.time, })?, - input_0indexed, + input, 0, )) }; @@ -388,7 +384,7 @@ impl NormalizedRowBuilder { /// Set the input compartment (1-indexed) /// /// Required for EVID=1 (dosing events). - /// Will be converted to 0-indexed internally. + /// Kept as 1-indexed; user must size state arrays accordingly. pub fn input(mut self, input: usize) -> Self { self.row.input = Some(input); self @@ -577,7 +573,7 @@ mod tests { Event::Observation(obs) => { assert_eq!(obs.time(), 1.0); assert_eq!(obs.value(), Some(25.5)); - assert_eq!(obs.outeq(), 0); // Converted to 0-indexed + assert_eq!(obs.outeq(), 1); // Kept as 1-indexed } _ => panic!("Expected observation event"), } @@ -598,7 +594,7 @@ mod tests { Event::Bolus(bolus) => { assert_eq!(bolus.time(), 0.0); assert_eq!(bolus.amount(), 100.0); - assert_eq!(bolus.input(), 0); // Converted to 0-indexed + assert_eq!(bolus.input(), 1); // Kept as 1-indexed } _ => panic!("Expected bolus event"), } @@ -621,7 +617,7 @@ mod tests { assert_eq!(inf.time(), 0.0); assert_eq!(inf.amount(), 100.0); assert_eq!(inf.duration(), 2.0); - assert_eq!(inf.input(), 0); + assert_eq!(inf.input(), 1); // Kept as 1-indexed } _ => panic!("Expected infusion event"), } diff --git a/src/data/parser/pmetrics.rs b/src/data/parser/pmetrics.rs index 8886561..b11ef64 100644 --- a/src/data/parser/pmetrics.rs +++ b/src/data/parser/pmetrics.rs @@ -315,7 +315,7 @@ impl Data { let value = obs .value() .map_or_else(|| ".".to_string(), |v| v.to_string()); - let outeq = (obs.outeq() + 1).to_string(); + let outeq = obs.outeq().to_string(); let censor = match obs.censoring() { Censor::None => "0".to_string(), Censor::BLOQ => "1".to_string(), @@ -372,7 +372,7 @@ impl Data { &inf.amount().to_string(), &".".to_string(), &".".to_string(), - &(inf.input() + 1).to_string(), + &inf.input().to_string(), &".".to_string(), &".".to_string(), &".".to_string(), @@ -393,7 +393,7 @@ impl Data { &bol.amount().to_string(), &".".to_string(), &".".to_string(), - &(bol.input() + 1).to_string(), + &bol.input().to_string(), &".".to_string(), &".".to_string(), &".".to_string(), @@ -466,8 +466,8 @@ mod tests { #[test] fn write_pmetrics_preserves_infusion_input() { let subject = Subject::builder("writer") - .infusion(0.0, 200.0, 2, 1.0) - .observation(1.0, 0.0, 0) + .infusion(0.0, 200.0, 3, 1.0) // input=3 (1-indexed) + .observation(1.0, 0.0, 1) // outeq=1 (1-indexed) .build(); let data = Data::new(vec![subject]); @@ -485,7 +485,7 @@ mod tests { .find(|record| record.get(3) != Some("0")) .expect("infusion row missing"); - assert_eq!(infusion_row.get(7), Some("3")); + assert_eq!(infusion_row.get(7), Some("3")); // Written as-is (1-indexed) } #[test]