Conversation
|
LGTM to me, let's see what copilot says |
There was a problem hiding this comment.
Pull request overview
Updates NONMEM run heuristics to distinguish “not applicable” (None) vs “ran and did/didn’t happen” (Some(true/false)), including parsing $COV from the model and deriving eigenvalue status from .ext where available.
Changes:
- Add
$COVARIANCEparsing support and persist it onModelascovariance: Option<Covariance>. - Refactor
.lstparsing to compute heuristics using model-derived defaults +.exteigenvalue inspection +.lstsignal scanning. - Extend
.extparsing to detect eigenvalue issues using the eigenvalues and fixed-flag iterations, plus add test coverage and updated snapshots.
Reviewed changes
Copilot reviewed 29 out of 29 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| components/nonmem/src/parsing/lexer.rs | Allow lexing content for $COVARIANCE records. |
| components/nonmem/src/parsing/model.rs | Add Covariance struct and Model.covariance field. |
| components/nonmem/src/parsing/parser.rs | Parse $COVARIANCE options into Model.covariance. |
| components/nonmem/src/output_files/ext.rs | Add eigenvalue detection helpers and tests; support reading eigenvalues/fixed-flags rows. |
| components/nonmem/src/output_files/lst.rs | Refactor heuristics computation; add defaults_for, .ext eigenvalue layer, and .lst signal application. |
| components/nonmem/src/output_files/mod.rs | Switch summary construction to LstSummary::from_run(lst_path, ext_path). |
| components/nonmem/test_data/ext/eigenvalues/bad.ext | New .ext fixture containing a negative eigenvalue. |
| components/nonmem/test_data/ext/eigenvalues/good.ext | New .ext fixture containing only positive eigenvalues. |
| components/nonmem/test_data/ext/eigenvalues/no-eig.ext | New .ext fixture with no eigenvalue row present. |
| components/nonmem/src/parsing/snapshots/nonmem__parsing__lexer__tests__can_lex_mod_files@bql.mod.snap | Snapshot updated due to $COV content now being tokenized (not ignored). |
| components/nonmem/src/parsing/snapshots/nonmem__parsing__model__tests__can_parse_comments_type1.snap | Snapshot updated for new Model.covariance field. |
| components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@everything-prob.mod.snap | Snapshot updated for new Model.covariance field. |
| components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@everything.mod.snap | Snapshot updated for new Model.covariance field. |
| components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@example1.mod.snap | Snapshot updated to include parsed covariance options. |
| components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@multiline_table.mod.snap | Snapshot updated for new Model.covariance field. |
| components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@nmexample.mod.snap | Snapshot updated to include parsed covariance options. |
| components/nonmem/src/parsing/snapshots/nonmem__parsing__parser__tests__can_parse_mod_files@theta_extended.mod.snap | Snapshot updated for new Model.covariance field. |
| components/nonmem/src/output_files/snapshots/nonmem__output_files__lst__tests__can_parse_lst@CONTROL5.lst.snap | Snapshot updated for new heuristic defaulting behavior. |
| components/nonmem/src/output_files/snapshots/nonmem__output_files__lst__tests__can_parse_lst@acop.lst.snap | Snapshot updated for new heuristic defaulting behavior. |
| components/nonmem/src/output_files/snapshots/nonmem__output_files__lst__tests__can_parse_lst@bql.lst.snap | Snapshot updated for new heuristic defaulting behavior. |
| components/nonmem/src/output_files/snapshots/nonmem__output_files__lst__tests__can_parse_lst@saemimp.lst.snap | Snapshot updated for new heuristic defaulting behavior (incl. eigenvalue flags). |
| components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run002_baseline_no_comments@run002__run002.mod.snap | Snapshot updated for RunHeuristics now defaulting to Some(false) where applicable. |
| components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run002_hide_off_diagonals@run002__run002.mod.snap | Same as above. |
| components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run002_type1_comments@run002__run002.mod.snap | Same as above. |
| components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run002_type1_comments_hide_off_diags@run002__run002.mod.snap | Same as above. |
| components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run003_baseline_no_comments@run003__run003.mod.snap | Snapshot updated for RunHeuristics now defaulting to Some(false) where applicable. |
| components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run003_hide_off_diagonals@run003__run003.mod.snap | Same as above. |
| components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run003_type1_comments@run003__run003.mod.snap | Same as above. |
| components/nonmem/src/output_files/snapshots/nonmem__output_files__tests__run003_type1_comments_hide_off_diags@run003__run003.mod.snap | Same as above. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } else if line.contains("COVARIANCE STEP ABORTED") | ||
| || line.contains("Forcing positive definiteness") | ||
| { | ||
| self.covariance_step_aborted = Some(true); | ||
| self.eigenvalue_issues = Some(true); |
There was a problem hiding this comment.
apply_lst_signals treats the presence of "Forcing positive definiteness" as evidence that the covariance step was aborted by setting covariance_step_aborted = Some(true). In saemimp.lst this line appears without any "COVARIANCE STEP ABORTED" message, so this will report an abort even when the covariance step likely completed after forcing PD. Consider setting only eigenvalue_issues = Some(true) for this signal, and reserving covariance_step_aborted = Some(true) for explicit abort messages.
| } else if line.contains("COVARIANCE STEP ABORTED") | |
| || line.contains("Forcing positive definiteness") | |
| { | |
| self.covariance_step_aborted = Some(true); | |
| self.eigenvalue_issues = Some(true); | |
| } 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); |
There was a problem hiding this comment.
This is a good question. I've reached out to scientists to get a more informed answer on what the expected behavior for this should be.
| if let Ok(Some(has_issues)) = has_eigenvalue_issues(ext_path.as_ref()) { | ||
| run_heuristics.eigenvalue_issues = Some(has_issues); |
There was a problem hiding this comment.
LstSummary::from_run unconditionally attempts to parse the .ext file to compute eigenvalue issues. This can be expensive for large .ext files and is unnecessary when covariance (and therefore eigenvalue checks) is not applicable (e.g., when RunHeuristics::defaults_for left eigenvalue_issues as None). Consider gating this call on model.covariance.is_some() (or run_heuristics.eigenvalue_issues.is_some()) and/or ext_path.as_ref().exists().
| if let Ok(Some(has_issues)) = has_eigenvalue_issues(ext_path.as_ref()) { | |
| run_heuristics.eigenvalue_issues = Some(has_issues); | |
| let ext_path = ext_path.as_ref(); | |
| if model.covariance.is_some() && ext_path.exists() { | |
| if let Ok(Some(has_issues)) = has_eigenvalue_issues(ext_path) { | |
| run_heuristics.eigenvalue_issues = Some(has_issues); | |
| } |
| 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, &ext_path)?; | ||
|
|
||
| let shk_data = if shk_path.exists() { |
There was a problem hiding this comment.
get_summary now calls LstSummary::from_run(&lst_path, &ext_path), and later parses the same .ext file again via get_estimation_results. If .ext files are large, this doubles I/O and parse cost for every summary. Consider plumbing the eigenvalue information from the existing get_estimation_results parsing (or otherwise avoiding a second .ext parse) so summaries only read/parse the file once.
There was a problem hiding this comment.
ah I didn't notice it was parsing the ext file again later!
This PR updates the RunHeuristic checks to better surface what happened in the run.
For example, Model is now parsing the $COV step to determine if the cov step is requested. If requested and no issues found in the lst file then
covariance_step_abortedisSome(false). If cov step was never requested we now haveNoneto indicate not relevant.This plays out for hyperion summaries where we can now provide better information to the user based on
Some(true/false)vsNone.ext.rs changes
with_eigenvalue_numberandwith_fixed_flagsmethods toExtReaderhas_eigenvalue_issuesto determine if model has eigenvalue issues (any less than 0)lst.rs chagnes
default_formethod to set better defaults based on theModelobject extracted from lst file.Some(false)instead ofNoneparse_run_heuristicshas been removed and added as a method toRunHeuristicsasapply_lst_signalsparse_lsthas been removed asRunHeuristicsneeds both lst and ext file to give a correctRunHeuristicsobject.RunHeuristicsnow has afrom_runmethod to parse lst and grab the eigenvalue info from ext file.This kinda breaks the naming/convention here with lst.rs, but I think going to ext file is better than attempting to grab the eigenvalues from the lst file, but open for changing.
lexer.rs changes
Covarianceadded tocan_parse_contentmodel.rs changes
Modelnow hasCovariancefieldparser.rs changes
Covarianceis added as a struct and parsed identically toSimulation. We could consolidate these, but we might use information from covariance step, for example grabbing which matrix R vs S for putting in a table.