diff --git a/components/nonmem/src/output_files/ext.rs b/components/nonmem/src/output_files/ext.rs index 0ef59ac..76feffc 100644 --- a/components/nonmem/src/output_files/ext.rs +++ b/components/nonmem/src/output_files/ext.rs @@ -7,6 +7,7 @@ use std::str::FromStr; use super::parsing::{self, ParseContext}; use crate::estimation::{EstimationMethod, extract_estimation_method}; use crate::output_files::shk::ShkTable; + use anyhow::{Result, bail}; use fs_err as fs; use rayon::prelude::*; diff --git a/components/nonmem/src/output_files/lst.rs b/components/nonmem/src/output_files/lst.rs index a28826a..c46fc43 100644 --- a/components/nonmem/src/output_files/lst.rs +++ b/components/nonmem/src/output_files/lst.rs @@ -24,6 +24,55 @@ pub struct RunHeuristics { pub minimization_terminated: Option, } +impl RunHeuristics { + /// Create heuristics with `Some(false)` defaults for checks that are + /// applicable given the model structure. Inapplicable checks stay `None`. + pub fn defaults_for(model: &Model) -> Self { + let mut h = Self::default(); + + let is_sim_only = model.simulation.as_ref().is_some_and(|s| s.is_only_sim()); + let has_estimation = !model.estimations.is_empty() && !is_sim_only; + + if model.covariance.is_some() { + h.covariance_step_aborted = Some(false); + h.eigenvalue_issues = Some(false); + } + if has_estimation { + h.minimization_terminated = Some(false); + h.hessian_reset = Some(false); + h.parameter_near_boundary = Some(false); + } + + h + } + + /// Apply heuristic signals found by scanning lst file lines. + /// This is the final layer and overrides any previously set values. + fn apply_lst_signals(&mut self, lines: &[&str]) { + for (idx, line) in lines.iter().enumerate() { + if line.contains("0MINIMIZATION TERMINATED") { + self.minimization_terminated = Some(true); + } else if line.contains("RESET HESSIAN") { + self.hessian_reset = Some(true); + } else if line.contains("PARAMETER ESTIMATE IS NEAR ITS BOUNDARY") { + self.parameter_near_boundary = Some(true); + } else if line.contains("EIGENVALUES OF COR MATRIX OF ESTIMAT") { + if let Some(has_issues) = parse_eigenvalue_issues(&lines[idx + 1..]) { + self.eigenvalue_issues = Some(has_issues); + } + } else if line.contains("COVARIANCE STEP ABORTED") { + self.covariance_step_aborted = Some(true); + self.eigenvalue_issues = Some(true); + } else if line.contains("Forcing positive definiteness") { + self.eigenvalue_issues = Some(true); + } else if line.contains("BEFORE THE COVARIANCE STEP CAN BE IMPLEMENTED") { + self.covariance_step_aborted = None; + self.eigenvalue_issues = None; + } + } + } +} + /// RunDetails contains key information about logistics of the model run #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] pub struct RunDetails { @@ -46,6 +95,23 @@ pub struct LstSummary { pub run_heuristics: RunHeuristics, } +impl LstSummary { + pub fn from_run(lst_path: impl AsRef) -> AnyhowResult { + let content = fs::read_to_string(lst_path.as_ref())?; + let model = extract_model_from_contents(&content)?; + let mut run_heuristics = RunHeuristics::defaults_for(&model); + + let lines: Vec<&str> = content.lines().collect(); + run_heuristics.apply_lst_signals(&lines); + + let run_details = parse_run_details(&content); + Ok(LstSummary { + run_details, + run_heuristics, + }) + } +} + fn parse_timing(line: &str) -> f64 { if let Some(captures) = SIGNED_NUMBER_RE.find(line) { captures.as_str().parse::().unwrap_or(0.0) @@ -102,40 +168,47 @@ fn parse_run_details(content: &str) -> RunDetails { run_details } -fn parse_run_heuristics(content: &str) -> RunHeuristics { - let mut run_heuristics = RunHeuristics::default(); +/// Parse eigenvalues from LST lines following an "EIGENVALUES OF COR MATRIX" header. +/// Returns `Some(true)` if any eigenvalue ≤ 0, `Some(false)` if all positive, `None` if none found. +fn parse_eigenvalue_issues(lines: &[&str]) -> Option { + let mut values = Vec::new(); + let mut found_values = false; - for line in content.lines() { - if line.contains("0MINIMIZATION TERMINATED") { - run_heuristics.minimization_terminated = Some(true); - } else if line.contains("RESET HESSIAN") { - run_heuristics.hessian_reset = Some(true); - } else if line.contains("PARAMETER ESTIMATE IS NEAR ITS BOUNDARY") { - run_heuristics.parameter_near_boundary = Some(true); - } else if line.contains("COVARIANCE STEP ABORTED") - || line.contains("Forcing positive definiteness") - { - run_heuristics.covariance_step_aborted = Some(true); + for line in lines { + let trimmed = line.trim(); + + if trimmed.is_empty() { + if found_values { + break; + } + continue; } - } - run_heuristics -} + // Skip asterisk formatting lines + if trimmed.starts_with('*') { + continue; + } -pub fn parse_lst(content: &str) -> LstSummary { - // This way we read the file multiple times but it's tiny and easier to understand for the dev - let run_heuristics = parse_run_heuristics(content); - let run_details = parse_run_details(content); + // Check if this line contains scientific notation (eigenvalue data) + if trimmed.contains('E') || trimmed.contains('e') { + for token in trimmed.split_whitespace() { + if let Ok(v) = token.parse::() { + values.push(v); + } + } + found_values = true; + } + // Otherwise it's an integer index line — skip it + } - LstSummary { - run_details, - run_heuristics, + if values.is_empty() { + None + } else { + Some(values.iter().any(|&v| v <= 0.0)) } } -pub fn extract_model(path: impl AsRef) -> AnyhowResult { - let contents = fs::read_to_string(path)?; - +pub fn extract_model_from_contents(contents: &str) -> AnyhowResult { // lst starts with timestamp, then model content, then NM-TRAN MESSAGES let model_content = contents .lines() @@ -148,6 +221,13 @@ pub fn extract_model(path: impl AsRef) -> AnyhowResult { Ok(model) } +pub fn extract_model(path: impl AsRef) -> AnyhowResult { + let contents = fs::read_to_string(path)?; + let model = extract_model_from_contents(&contents)?; + + Ok(model) +} + #[cfg(test)] mod tests { use super::*; @@ -159,8 +239,7 @@ mod tests { use std::path::PathBuf; let test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test_data/lst"); glob!(test_dir, "*.lst", |path| { - let input = fs::read_to_string(path).unwrap(); - assert_debug_snapshot!(parse_lst(&input)); + assert_debug_snapshot!(LstSummary::from_run(path).unwrap()) }); } diff --git a/components/nonmem/src/output_files/mod.rs b/components/nonmem/src/output_files/mod.rs index 6d5d504..1a12604 100644 --- a/components/nonmem/src/output_files/mod.rs +++ b/components/nonmem/src/output_files/mod.rs @@ -6,7 +6,7 @@ use crate::output_files::cor::{CorReader, CorrelationMatrix}; use crate::output_files::ext::{ ExtReader, MinimizationResults, ParameterType, TableParameters, get_estimation_results, }; -use crate::output_files::lst::{LstSummary, parse_lst}; +use crate::output_files::lst::LstSummary; use crate::output_files::shk::ShkReader; use crate::{Model, TERMINATION_FILENAME, Termination}; use anyhow::{Result, anyhow, bail}; @@ -91,7 +91,7 @@ pub fn get_summary( let mut model = Model::parse(&fs::read_to_string(model_path)?)?; let parameter_names = model.get_parameter_names(comment_type)?; - let lst_summary = parse_lst(&fs::read_to_string(&lst_path)?); + let lst_summary = LstSummary::from_run(&lst_path)?; let shk_data = if shk_path.exists() { ShkReader.parse_file(shk_path)? diff --git a/components/nonmem/src/output_files/snapshots/nonmem__output_files__lst__tests__can_parse_lst@CONTROL5.lst.snap b/components/nonmem/src/output_files/snapshots/nonmem__output_files__lst__tests__can_parse_lst@CONTROL5.lst.snap index a17c920..dc9c254 100644 --- a/components/nonmem/src/output_files/snapshots/nonmem__output_files__lst__tests__can_parse_lst@CONTROL5.lst.snap +++ b/components/nonmem/src/output_files/snapshots/nonmem__output_files__lst__tests__can_parse_lst@CONTROL5.lst.snap @@ -24,8 +24,14 @@ LstSummary { run_heuristics: RunHeuristics { covariance_step_aborted: None, eigenvalue_issues: None, - parameter_near_boundary: None, - hessian_reset: None, - minimization_terminated: None, + parameter_near_boundary: Some( + false, + ), + hessian_reset: Some( + false, + ), + minimization_terminated: Some( + false, + ), }, } diff --git a/components/nonmem/src/output_files/snapshots/nonmem__output_files__lst__tests__can_parse_lst@acop.lst.snap b/components/nonmem/src/output_files/snapshots/nonmem__output_files__lst__tests__can_parse_lst@acop.lst.snap index da2a8e9..5c0ec08 100644 --- a/components/nonmem/src/output_files/snapshots/nonmem__output_files__lst__tests__can_parse_lst@acop.lst.snap +++ b/components/nonmem/src/output_files/snapshots/nonmem__output_files__lst__tests__can_parse_lst@acop.lst.snap @@ -1,6 +1,6 @@ --- source: components/nonmem/src/output_files/lst.rs -expression: parse_lst(&input) +expression: parse_lst_content(&input).unwrap() input_file: components/nonmem/test_data/lst/acop.lst --- LstSummary { @@ -24,10 +24,20 @@ LstSummary { only_sim: false, }, run_heuristics: RunHeuristics { - covariance_step_aborted: None, - eigenvalue_issues: None, - parameter_near_boundary: None, - hessian_reset: None, - minimization_terminated: None, + covariance_step_aborted: Some( + false, + ), + eigenvalue_issues: Some( + false, + ), + parameter_near_boundary: Some( + false, + ), + hessian_reset: Some( + false, + ), + minimization_terminated: Some( + false, + ), }, } diff --git a/components/nonmem/src/output_files/snapshots/nonmem__output_files__lst__tests__can_parse_lst@bql.lst.snap b/components/nonmem/src/output_files/snapshots/nonmem__output_files__lst__tests__can_parse_lst@bql.lst.snap index bfd258a..235e57a 100644 --- a/components/nonmem/src/output_files/snapshots/nonmem__output_files__lst__tests__can_parse_lst@bql.lst.snap +++ b/components/nonmem/src/output_files/snapshots/nonmem__output_files__lst__tests__can_parse_lst@bql.lst.snap @@ -1,6 +1,6 @@ --- source: components/nonmem/src/output_files/lst.rs -expression: parse_lst(&input) +expression: parse_lst_content(&input).unwrap() input_file: components/nonmem/test_data/lst/bql.lst --- LstSummary { @@ -24,10 +24,20 @@ LstSummary { only_sim: false, }, run_heuristics: RunHeuristics { - covariance_step_aborted: None, - eigenvalue_issues: None, - parameter_near_boundary: None, - hessian_reset: None, - minimization_terminated: None, + covariance_step_aborted: Some( + false, + ), + eigenvalue_issues: Some( + false, + ), + parameter_near_boundary: Some( + false, + ), + hessian_reset: Some( + false, + ), + minimization_terminated: Some( + false, + ), }, } diff --git a/components/nonmem/src/output_files/snapshots/nonmem__output_files__lst__tests__can_parse_lst@saemimp.lst.snap b/components/nonmem/src/output_files/snapshots/nonmem__output_files__lst__tests__can_parse_lst@saemimp.lst.snap index b8a65b7..cf8f837 100644 --- a/components/nonmem/src/output_files/snapshots/nonmem__output_files__lst__tests__can_parse_lst@saemimp.lst.snap +++ b/components/nonmem/src/output_files/snapshots/nonmem__output_files__lst__tests__can_parse_lst@saemimp.lst.snap @@ -1,6 +1,6 @@ --- source: components/nonmem/src/output_files/lst.rs -expression: parse_lst(&input) +expression: "LstSummary::from_run(path).unwrap()" input_file: components/nonmem/test_data/lst/saemimp.lst --- LstSummary { @@ -28,11 +28,19 @@ LstSummary { }, run_heuristics: RunHeuristics { covariance_step_aborted: Some( + false, + ), + eigenvalue_issues: Some( true, ), - eigenvalue_issues: None, - parameter_near_boundary: None, - hessian_reset: None, - minimization_terminated: None, + parameter_near_boundary: Some( + false, + ), + hessian_reset: Some( + false, + ), + minimization_terminated: Some( + false, + ), }, } diff --git a/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run002_baseline_no_comments@run002__run002.mod.snap b/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run002_baseline_no_comments@run002__run002.mod.snap index 6888c58..ed54705 100644 --- a/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run002_baseline_no_comments@run002__run002.mod.snap +++ b/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run002_baseline_no_comments@run002__run002.mod.snap @@ -26,11 +26,21 @@ Summary { only_sim: false, }, run_heuristics: RunHeuristics { - covariance_step_aborted: None, - eigenvalue_issues: None, - parameter_near_boundary: None, - hessian_reset: None, - minimization_terminated: None, + covariance_step_aborted: Some( + false, + ), + eigenvalue_issues: Some( + false, + ), + parameter_near_boundary: Some( + false, + ), + hessian_reset: Some( + false, + ), + minimization_terminated: Some( + false, + ), }, }, minimization_results: [ diff --git a/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run002_hide_off_diagonals@run002__run002.mod.snap b/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run002_hide_off_diagonals@run002__run002.mod.snap index 6888c58..ed54705 100644 --- a/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run002_hide_off_diagonals@run002__run002.mod.snap +++ b/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run002_hide_off_diagonals@run002__run002.mod.snap @@ -26,11 +26,21 @@ Summary { only_sim: false, }, run_heuristics: RunHeuristics { - covariance_step_aborted: None, - eigenvalue_issues: None, - parameter_near_boundary: None, - hessian_reset: None, - minimization_terminated: None, + covariance_step_aborted: Some( + false, + ), + eigenvalue_issues: Some( + false, + ), + parameter_near_boundary: Some( + false, + ), + hessian_reset: Some( + false, + ), + minimization_terminated: Some( + false, + ), }, }, minimization_results: [ diff --git a/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run002_type1_comments@run002__run002.mod.snap b/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run002_type1_comments@run002__run002.mod.snap index 295f805..c130e61 100644 --- a/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run002_type1_comments@run002__run002.mod.snap +++ b/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run002_type1_comments@run002__run002.mod.snap @@ -26,11 +26,21 @@ Summary { only_sim: false, }, run_heuristics: RunHeuristics { - covariance_step_aborted: None, - eigenvalue_issues: None, - parameter_near_boundary: None, - hessian_reset: None, - minimization_terminated: None, + covariance_step_aborted: Some( + false, + ), + eigenvalue_issues: Some( + false, + ), + parameter_near_boundary: Some( + false, + ), + hessian_reset: Some( + false, + ), + minimization_terminated: Some( + false, + ), }, }, minimization_results: [ diff --git a/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run002_type1_comments_hide_off_diags@run002__run002.mod.snap b/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run002_type1_comments_hide_off_diags@run002__run002.mod.snap index 295f805..c130e61 100644 --- a/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run002_type1_comments_hide_off_diags@run002__run002.mod.snap +++ b/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run002_type1_comments_hide_off_diags@run002__run002.mod.snap @@ -26,11 +26,21 @@ Summary { only_sim: false, }, run_heuristics: RunHeuristics { - covariance_step_aborted: None, - eigenvalue_issues: None, - parameter_near_boundary: None, - hessian_reset: None, - minimization_terminated: None, + covariance_step_aborted: Some( + false, + ), + eigenvalue_issues: Some( + false, + ), + parameter_near_boundary: Some( + false, + ), + hessian_reset: Some( + false, + ), + minimization_terminated: Some( + false, + ), }, }, minimization_results: [ diff --git a/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run003_baseline_no_comments@run003__run003.mod.snap b/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run003_baseline_no_comments@run003__run003.mod.snap index b13e9ba..aef0ca6 100644 --- a/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run003_baseline_no_comments@run003__run003.mod.snap +++ b/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run003_baseline_no_comments@run003__run003.mod.snap @@ -26,11 +26,21 @@ Summary { only_sim: false, }, run_heuristics: RunHeuristics { - covariance_step_aborted: None, - eigenvalue_issues: None, - parameter_near_boundary: None, - hessian_reset: None, - minimization_terminated: None, + covariance_step_aborted: Some( + false, + ), + eigenvalue_issues: Some( + false, + ), + parameter_near_boundary: Some( + false, + ), + hessian_reset: Some( + false, + ), + minimization_terminated: Some( + false, + ), }, }, minimization_results: [ diff --git a/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run003_hide_off_diagonals@run003__run003.mod.snap b/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run003_hide_off_diagonals@run003__run003.mod.snap index 1d4ee74..c7ce072 100644 --- a/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run003_hide_off_diagonals@run003__run003.mod.snap +++ b/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run003_hide_off_diagonals@run003__run003.mod.snap @@ -26,11 +26,21 @@ Summary { only_sim: false, }, run_heuristics: RunHeuristics { - covariance_step_aborted: None, - eigenvalue_issues: None, - parameter_near_boundary: None, - hessian_reset: None, - minimization_terminated: None, + covariance_step_aborted: Some( + false, + ), + eigenvalue_issues: Some( + false, + ), + parameter_near_boundary: Some( + false, + ), + hessian_reset: Some( + false, + ), + minimization_terminated: Some( + false, + ), }, }, minimization_results: [ diff --git a/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run003_type1_comments@run003__run003.mod.snap b/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run003_type1_comments@run003__run003.mod.snap index 9a64d67..abc8780 100644 --- a/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run003_type1_comments@run003__run003.mod.snap +++ b/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run003_type1_comments@run003__run003.mod.snap @@ -26,11 +26,21 @@ Summary { only_sim: false, }, run_heuristics: RunHeuristics { - covariance_step_aborted: None, - eigenvalue_issues: None, - parameter_near_boundary: None, - hessian_reset: None, - minimization_terminated: None, + covariance_step_aborted: Some( + false, + ), + eigenvalue_issues: Some( + false, + ), + parameter_near_boundary: Some( + false, + ), + hessian_reset: Some( + false, + ), + minimization_terminated: Some( + false, + ), }, }, minimization_results: [ diff --git a/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run003_type1_comments_hide_off_diags@run003__run003.mod.snap b/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run003_type1_comments_hide_off_diags@run003__run003.mod.snap index f90a0be..9b58cde 100644 --- a/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run003_type1_comments_hide_off_diags@run003__run003.mod.snap +++ b/components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run003_type1_comments_hide_off_diags@run003__run003.mod.snap @@ -26,11 +26,21 @@ Summary { only_sim: false, }, run_heuristics: RunHeuristics { - covariance_step_aborted: None, - eigenvalue_issues: None, - parameter_near_boundary: None, - hessian_reset: None, - minimization_terminated: None, + covariance_step_aborted: Some( + false, + ), + eigenvalue_issues: Some( + false, + ), + parameter_near_boundary: Some( + false, + ), + hessian_reset: Some( + false, + ), + minimization_terminated: Some( + false, + ), }, }, minimization_results: [ diff --git a/components/nonmem/src/parsing/lexer.rs b/components/nonmem/src/parsing/lexer.rs index 76f9ce5..c38b74a 100644 --- a/components/nonmem/src/parsing/lexer.rs +++ b/components/nonmem/src/parsing/lexer.rs @@ -42,6 +42,7 @@ impl ControlRecord { | ControlRecord::Omega | ControlRecord::Sigma | ControlRecord::Estimation + | ControlRecord::Covariance | ControlRecord::Simulation | ControlRecord::Table ) diff --git a/components/nonmem/src/parsing/model.rs b/components/nonmem/src/parsing/model.rs index 42fe0d3..aae818f 100644 --- a/components/nonmem/src/parsing/model.rs +++ b/components/nonmem/src/parsing/model.rs @@ -295,6 +295,12 @@ impl Simulation { } } +#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] +pub struct Covariance { + #[serde(default)] + pub options: BTreeMap>, +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Subroutine { Builtin { @@ -336,6 +342,7 @@ pub struct Model { pub estimations: Vec, pub tables: Vec, pub simulation: Option, + pub covariance: Option, // Token range tracking for editing pub token_ranges: ModelTokenRanges, // Original tokens for reconstruction @@ -355,6 +362,7 @@ impl fmt::Debug for Model { .field("estimations", &self.estimations) .field("tables", &self.tables) .field("simulation", &self.simulation) + .field("covariance", &self.covariance) .finish() } } diff --git a/components/nonmem/src/parsing/parser.rs b/components/nonmem/src/parsing/parser.rs index 39d0290..f8e8223 100644 --- a/components/nonmem/src/parsing/parser.rs +++ b/components/nonmem/src/parsing/parser.rs @@ -3,8 +3,9 @@ use crate::parsing::comments::ParamName; use crate::parsing::errors::SyntaxError; use crate::parsing::lexer::ControlRecord; use crate::parsing::model::{ - BlockStructure, ComparisonOperator, Data, DataFilter, DataValueFilter, DataValueFilterKind, - Estimation, InputColumn, Parameter, ParameterBlock, Parameterization, Simulation, Subroutine, + BlockStructure, ComparisonOperator, Covariance, Data, DataFilter, DataValueFilter, + DataValueFilterKind, Estimation, InputColumn, Parameter, ParameterBlock, Parameterization, + Simulation, Subroutine, }; use crate::parsing::utils::{Span, Spanned}; use crate::parsing::{Model, Token, lex}; @@ -835,6 +836,21 @@ impl Parser { Ok(simulation) } + fn parse_covariance(&mut self) -> Result { + let mut covariance = Covariance::default(); + + while let Some((peeked, _)) = self.peek_non_trivia() + && !matches!(peeked, Token::ControlRecord { .. }) + { + let (token, _) = self.next_non_trivia_or_error()?; + if let Some((key, value)) = self.parse_option(&token)? { + covariance.options.insert(key, value); + } + } + + Ok(covariance) + } + /// Shared helper method to parse block content after BLOCK(N) syntax /// Handles post-BLOCK keywords (CORR, SD, CHOLESKY, SAME, FIX, VALUES) and parameter parsing /// Returns (parameters, token_indices, final_parametrization, final_same_flag) @@ -1163,7 +1179,9 @@ impl Parser { self.model.estimations.push(estimation); self.model.token_ranges.estimations.push(indices); } - ControlRecord::Covariance => {} + ControlRecord::Covariance => { + self.model.covariance = Some(self.parse_covariance()?); + } ControlRecord::Model => {} ControlRecord::Des => {} ControlRecord::Simulation => { diff --git a/components/nonmem/src/parsing/snapshots/nonmem__parsing__lexer__tests__can_lex_mod_files@bql.mod.snap b/components/nonmem/src/parsing/snapshots/nonmem__parsing__lexer__tests__can_lex_mod_files@bql.mod.snap index 67dfe34..0b64964 100644 --- a/components/nonmem/src/parsing/snapshots/nonmem__parsing__lexer__tests__can_lex_mod_files@bql.mod.snap +++ b/components/nonmem/src/parsing/snapshots/nonmem__parsing__lexer__tests__can_lex_mod_files@bql.mod.snap @@ -140,7 +140,11 @@ Ok( COMMENT( pro res error) @ 26:14-26:29 (567..582), WHITESPACE("\n") @ 26:29-27:0 (582..583), CONTROL_RECORD(Covariance) @ 27:0-27:4 (583..587), - IGNORED(" PRINT=E\n") @ 27:4-28:0 (587..596), + WHITESPACE(" ") @ 27:4-27:5 (587..588), + IDENT(PRINT) @ 27:5-27:10 (588..593), + EQUALS @ 27:10-27:11 (593..594), + IDENT(E) @ 27:11-27:12 (594..595), + WHITESPACE("\n") @ 27:12-28:0 (595..596), CONTROL_RECORD(Estimation) @ 28:0-28:4 (596..600), WHITESPACE(" ") @ 28:4-28:5 (600..601), IDENT(MAXEVAL) @ 28:5-28:12 (601..608), diff --git a/components/nonmem/src/parsing/snapshots/nonmem__parsing__model__tests__can_parse_comments_type1.snap b/components/nonmem/src/parsing/snapshots/nonmem__parsing__model__tests__can_parse_comments_type1.snap index c36b307..861f228 100644 --- a/components/nonmem/src/parsing/snapshots/nonmem__parsing__model__tests__can_parse_comments_type1.snap +++ b/components/nonmem/src/parsing/snapshots/nonmem__parsing__model__tests__can_parse_comments_type1.snap @@ -315,4 +315,5 @@ Model { estimations: [], tables: [], simulation: None, + covariance: None, } diff --git a/components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@everything-prob.mod.snap b/components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@everything-prob.mod.snap index b1a84ba..b769376 100644 --- a/components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@everything-prob.mod.snap +++ b/components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@everything-prob.mod.snap @@ -410,4 +410,5 @@ Model { }, }, ), + covariance: None, } diff --git a/components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@everything.mod.snap b/components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@everything.mod.snap index fa809ec..758712d 100644 --- a/components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@everything.mod.snap +++ b/components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@everything.mod.snap @@ -957,4 +957,5 @@ Model { }, }, ), + covariance: None, } diff --git a/components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@example1.mod.snap b/components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@example1.mod.snap index e92988b..eab585f 100644 --- a/components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@example1.mod.snap +++ b/components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@example1.mod.snap @@ -417,4 +417,20 @@ Model { "example1.ETA", ], simulation: None, + covariance: Some( + Covariance { + options: { + "MATRIX": Some( + "R", + ), + "PRINT": Some( + "E", + ), + "SIGL": Some( + "12", + ), + "UNCONDITIONAL": None, + }, + }, + ), } diff --git a/components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@multiline_table.mod.snap b/components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@multiline_table.mod.snap index 4248ffc..d72abd2 100644 --- a/components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@multiline_table.mod.snap +++ b/components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@multiline_table.mod.snap @@ -115,4 +115,5 @@ Model { "run.param.tab", ], simulation: None, + covariance: None, } diff --git a/components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@nmexample.mod.snap b/components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@nmexample.mod.snap index 528a3b7..438297d 100644 --- a/components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@nmexample.mod.snap +++ b/components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@nmexample.mod.snap @@ -417,4 +417,20 @@ Model { "example1.ETA", ], simulation: None, + covariance: Some( + Covariance { + options: { + "MATRIX": Some( + "R", + ), + "PRINT": Some( + "E", + ), + "SIGL": Some( + "12", + ), + "UNCONDITIONAL": None, + }, + }, + ), } diff --git a/components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@theta_extended.mod.snap b/components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@theta_extended.mod.snap index f1bf49a..d035639 100644 --- a/components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@theta_extended.mod.snap +++ b/components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@theta_extended.mod.snap @@ -349,4 +349,5 @@ Model { estimations: [], tables: [], simulation: None, + covariance: None, } diff --git a/components/nonmem/test_data/ext/eigenvalues/bad.ext b/components/nonmem/test_data/ext/eigenvalues/bad.ext new file mode 100644 index 0000000..21283a9 --- /dev/null +++ b/components/nonmem/test_data/ext/eigenvalues/bad.ext @@ -0,0 +1,19 @@ +TABLE NO. 1: First Order Conditional Estimation with Interaction: Goal Function=MINIMUM VALUE OF OBJECTIVE FUNCTION: Problem=1 Subproblem=0 Superproblem1=0 Iteration1=0 Superproblem2=0 Iteration2=0 + ITERATION THETA1 THETA2 THETA3 SIGMA(1,1) SIGMA(2,1) SIGMA(2,2) OMEGA(1,1) OMEGA(2,1) OMEGA(2,2) OMEGA(3,1) OMEGA(3,2) OMEGA(3,3) OBJ + 0 1.61150E+00 3.61810E+01 1.00380E+00 3.57380E-02 0.00000E+00 6.00000E-03 1.00000E-01 1.00000E-04 1.00000E-01 0.00000E+00 0.00000E+00 1.00000E-01 -86.959373274665197 + 5 1.25128E+00 4.06637E+01 1.24588E+00 3.66667E-02 0.00000E+00 6.03304E-03 1.26194E-01 1.16961E-04 1.32484E-01 0.00000E+00 0.00000E+00 1.06678E-01 -103.40028105047986 + 10 1.24739E+00 4.07289E+01 1.23957E+00 3.75306E-02 0.00000E+00 6.26455E-03 1.31367E-01 4.51769E-04 1.37124E-01 0.00000E+00 0.00000E+00 1.13987E-01 -103.52334716673808 + 15 1.32629E+00 4.00619E+01 1.21176E+00 3.91431E-02 0.00000E+00 9.17957E-04 1.26098E-01 7.33796E-02 1.22765E-01 0.00000E+00 0.00000E+00 1.20713E-01 -109.61415479094936 + 20 1.32476E+00 4.00903E+01 1.21017E+00 3.74384E-02 0.00000E+00 5.54481E-03 1.22056E-01 7.46234E-02 1.23887E-01 0.00000E+00 0.00000E+00 1.22251E-01 -109.82438015775506 + 25 1.32485E+00 4.01325E+01 1.21127E+00 3.75358E-02 0.00000E+00 5.26704E-03 1.22334E-01 7.45863E-02 1.23882E-01 0.00000E+00 0.00000E+00 1.22373E-01 -109.82569562011314 + 30 1.32541E+00 4.01625E+01 1.21172E+00 3.75369E-02 0.00000E+00 5.27248E-03 1.22341E-01 7.45433E-02 1.23878E-01 0.00000E+00 0.00000E+00 1.22413E-01 -109.82582404780803 + 34 1.32542E+00 4.01625E+01 1.21172E+00 3.75371E-02 0.00000E+00 5.27228E-03 1.22342E-01 7.45433E-02 1.23878E-01 0.00000E+00 0.00000E+00 1.22412E-01 -109.82582410548454 + -1000000000 1.32542E+00 4.01625E+01 1.21172E+00 3.75371E-02 0.00000E+00 5.27228E-03 1.22342E-01 7.45433E-02 1.23878E-01 0.00000E+00 0.00000E+00 1.22412E-01 -109.82582410548454 + -1000000001 1.11484E-01 2.83899E+00 1.09747E-01 6.03493E-03 1.00000E+10 9.21096E-03 5.03554E-02 3.13350E-02 3.67465E-02 1.00000E+10 1.00000E+10 5.62781E-02 0.0000000000000000 + -1000000002 2.79219E-01 4.48008E-01 5.33300E-01 7.73906E-01 1.06718E+00 1.23401E+00 1.25571E+00 1.68544E+00 -1.72323E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.0000000000000000 + -1000000003 6.17163E+00 2.79219E-01 1.72323E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.0000000000000000 + -1000000004 0.00000E+00 0.00000E+00 0.00000E+00 1.93745E-01 0.00000E+00 7.26105E-02 3.49774E-01 6.05513E-01 3.51963E-01 0.00000E+00 0.00000E+00 3.49874E-01 0.0000000000000000 + -1000000005 0.00000E+00 0.00000E+00 0.00000E+00 1.55744E-02 1.00000E+10 6.34272E-02 7.19828E-02 1.93797E-01 5.22022E-02 1.00000E+10 1.00000E+10 8.04262E-02 0.0000000000000000 + -1000000006 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 1.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 1.00000E+00 1.00000E+00 0.00000E+00 0.0000000000000000 + -1000000007 0.00000E+00 3.70000E+01 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.0000000000000000 + -1000000008 5.29450E-04 -7.76032E-06 -5.30223E-06 1.96517E-02 0.00000E+00 1.11474E-02 7.53222E-04 -7.40068E-04 2.11213E-04 0.00000E+00 0.00000E+00 1.04945E-04 0.0000000000000000 diff --git a/components/nonmem/test_data/ext/eigenvalues/good.ext b/components/nonmem/test_data/ext/eigenvalues/good.ext new file mode 100644 index 0000000..5670949 --- /dev/null +++ b/components/nonmem/test_data/ext/eigenvalues/good.ext @@ -0,0 +1,19 @@ +TABLE NO. 1: First Order Conditional Estimation with Interaction: Goal Function=MINIMUM VALUE OF OBJECTIVE FUNCTION: Problem=1 Subproblem=0 Superproblem1=0 Iteration1=0 Superproblem2=0 Iteration2=0 + ITERATION THETA1 THETA2 THETA3 SIGMA(1,1) SIGMA(2,1) SIGMA(2,2) OMEGA(1,1) OMEGA(2,1) OMEGA(2,2) OMEGA(3,1) OMEGA(3,2) OMEGA(3,3) OBJ + 0 1.61150E+00 3.61810E+01 1.00380E+00 3.57380E-02 0.00000E+00 6.00000E-03 1.00000E-01 1.00000E-04 1.00000E-01 0.00000E+00 0.00000E+00 1.00000E-01 -86.959373274665197 + 5 1.25128E+00 4.06637E+01 1.24588E+00 3.66667E-02 0.00000E+00 6.03304E-03 1.26194E-01 1.16961E-04 1.32484E-01 0.00000E+00 0.00000E+00 1.06678E-01 -103.40028105047986 + 10 1.24739E+00 4.07289E+01 1.23957E+00 3.75306E-02 0.00000E+00 6.26455E-03 1.31367E-01 4.51769E-04 1.37124E-01 0.00000E+00 0.00000E+00 1.13987E-01 -103.52334716673808 + 15 1.32629E+00 4.00619E+01 1.21176E+00 3.91431E-02 0.00000E+00 9.17957E-04 1.26098E-01 7.33796E-02 1.22765E-01 0.00000E+00 0.00000E+00 1.20713E-01 -109.61415479094936 + 20 1.32476E+00 4.00903E+01 1.21017E+00 3.74384E-02 0.00000E+00 5.54481E-03 1.22056E-01 7.46234E-02 1.23887E-01 0.00000E+00 0.00000E+00 1.22251E-01 -109.82438015775506 + 25 1.32485E+00 4.01325E+01 1.21127E+00 3.75358E-02 0.00000E+00 5.26704E-03 1.22334E-01 7.45863E-02 1.23882E-01 0.00000E+00 0.00000E+00 1.22373E-01 -109.82569562011314 + 30 1.32541E+00 4.01625E+01 1.21172E+00 3.75369E-02 0.00000E+00 5.27248E-03 1.22341E-01 7.45433E-02 1.23878E-01 0.00000E+00 0.00000E+00 1.22413E-01 -109.82582404780803 + 34 1.32542E+00 4.01625E+01 1.21172E+00 3.75371E-02 0.00000E+00 5.27228E-03 1.22342E-01 7.45433E-02 1.23878E-01 0.00000E+00 0.00000E+00 1.22412E-01 -109.82582410548454 + -1000000000 1.32542E+00 4.01625E+01 1.21172E+00 3.75371E-02 0.00000E+00 5.27228E-03 1.22342E-01 7.45433E-02 1.23878E-01 0.00000E+00 0.00000E+00 1.22412E-01 -109.82582410548454 + -1000000001 1.11484E-01 2.83899E+00 1.09747E-01 6.03493E-03 1.00000E+10 9.21096E-03 5.03554E-02 3.13350E-02 3.67465E-02 1.00000E+10 1.00000E+10 5.62781E-02 0.0000000000000000 + -1000000002 2.79219E-01 4.48008E-01 5.33300E-01 7.73906E-01 1.06718E+00 1.23401E+00 1.25571E+00 1.68544E+00 1.72323E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.0000000000000000 + -1000000003 6.17163E+00 2.79219E-01 1.72323E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.0000000000000000 + -1000000004 0.00000E+00 0.00000E+00 0.00000E+00 1.93745E-01 0.00000E+00 7.26105E-02 3.49774E-01 6.05513E-01 3.51963E-01 0.00000E+00 0.00000E+00 3.49874E-01 0.0000000000000000 + -1000000005 0.00000E+00 0.00000E+00 0.00000E+00 1.55744E-02 1.00000E+10 6.34272E-02 7.19828E-02 1.93797E-01 5.22022E-02 1.00000E+10 1.00000E+10 8.04262E-02 0.0000000000000000 + -1000000006 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 1.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 1.00000E+00 1.00000E+00 0.00000E+00 0.0000000000000000 + -1000000007 0.00000E+00 3.70000E+01 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.0000000000000000 + -1000000008 5.29450E-04 -7.76032E-06 -5.30223E-06 1.96517E-02 0.00000E+00 1.11474E-02 7.53222E-04 -7.40068E-04 2.11213E-04 0.00000E+00 0.00000E+00 1.04945E-04 0.0000000000000000 diff --git a/components/nonmem/test_data/ext/eigenvalues/no-eig.ext b/components/nonmem/test_data/ext/eigenvalues/no-eig.ext new file mode 100644 index 0000000..30c44e9 --- /dev/null +++ b/components/nonmem/test_data/ext/eigenvalues/no-eig.ext @@ -0,0 +1,13 @@ +TABLE NO. 1: First Order Conditional Estimation with Interaction: Goal Function=MINIMUM VALUE OF OBJECTIVE FUNCTION: Problem=1 Subproblem=0 Superproblem1=0 Iteration1=0 Superproblem2=0 Iteration2=0 + ITERATION THETA1 THETA2 THETA3 THETA4 SIGMA(1,1) SIGMA(2,1) SIGMA(2,2) OMEGA(1,1) OMEGA(2,1) OMEGA(2,2) OMEGA(3,1) OMEGA(3,2) OMEGA(3,3) OBJ + 0 1.76862E+00 5.00000E+00 3.48030E+01 9.39213E-01 3.46496E-02 0.00000E+00 6.02000E-03 1.03000E-01 9.00000E-05 1.09000E-01 0.00000E+00 0.00000E+00 9.90000E-02 192.04123425887514 + 5 9.44506E-01 1.82172E+00 3.94815E+01 1.45823E+00 3.64489E-02 0.00000E+00 6.43935E-03 9.41781E-02 8.98901E-05 1.56510E-01 0.00000E+00 0.00000E+00 1.34558E-01 -89.219279864685959 + 10 1.05047E+00 1.56147E+00 4.14271E+01 1.23713E+00 3.83761E-02 0.00000E+00 5.96368E-03 8.82135E-02 2.25698E-04 1.39442E-01 0.00000E+00 0.00000E+00 1.13660E-01 -94.801203760939828 + 15 1.19176E+00 5.93542E-01 4.06065E+01 1.24415E+00 3.63503E-02 0.00000E+00 9.12589E-03 1.24611E-01 7.04238E-03 1.33894E-01 0.00000E+00 0.00000E+00 1.05848E-01 -103.76206300269818 + 20 1.24392E+00 5.45000E-01 4.02648E+01 1.21742E+00 3.73843E-02 0.00000E+00 5.95858E-03 1.22616E-01 7.24333E-02 1.23651E-01 0.00000E+00 0.00000E+00 1.24088E-01 -108.88169909012107 + 25 1.24949E+00 5.45000E-01 4.02343E+01 1.21696E+00 3.73316E-02 0.00000E+00 5.89876E-03 1.23219E-01 7.21938E-02 1.24725E-01 0.00000E+00 0.00000E+00 1.23868E-01 -108.88847177588016 + 27 1.25027E+00 5.45000E-01 4.02813E+01 1.21766E+00 3.73509E-02 0.00000E+00 5.89417E-03 1.23267E-01 7.21799E-02 1.24605E-01 0.00000E+00 0.00000E+00 1.23881E-01 -108.88878754052826 + -1000000000 1.25027E+00 5.45000E-01 4.02813E+01 1.21766E+00 3.73509E-02 0.00000E+00 5.89417E-03 1.23267E-01 7.21799E-02 1.24605E-01 0.00000E+00 0.00000E+00 1.23881E-01 -108.88878754052826 + -1000000004 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 1.93264E-01 0.00000E+00 7.67735E-02 3.51094E-01 5.82404E-01 3.52994E-01 0.00000E+00 0.00000E+00 3.51968E-01 0.0000000000000000 + -1000000006 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 1.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 1.00000E+00 1.00000E+00 0.00000E+00 0.0000000000000000 + -1000000007 0.00000E+00 3.70000E+01 4.00000E+01 4.40000E+01 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.0000000000000000