From fcd44c98dd3d14ffad40155dfb8d30731fa43de0 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Thu, 26 Mar 2026 14:52:51 -0400 Subject: [PATCH 01/41] wip --- NAMESPACE | 1 + R/extendr-wrappers.R | 12 + man/read_nmmodel.Rd | 22 ++ src/rust/Cargo.lock | 70 +++- src/rust/Cargo.toml | 4 +- src/rust/lib.rs | 1 + src/rust/nmparser/Cargo.toml | 22 ++ src/rust/nmparser/src/lib.rs | 10 + src/rust/nmparser/src/nmmodel/mod.rs | 98 ++++++ src/rust/nmparser/src/utils.rs | 462 +++++++++++++++++++++++++++ 10 files changed, 699 insertions(+), 3 deletions(-) create mode 100644 man/read_nmmodel.Rd create mode 100644 src/rust/nmparser/Cargo.toml create mode 100644 src/rust/nmparser/src/lib.rs create mode 100644 src/rust/nmparser/src/nmmodel/mod.rs create mode 100644 src/rust/nmparser/src/utils.rs diff --git a/NAMESPACE b/NAMESPACE index cb6c4438..1f9c7dd8 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -67,6 +67,7 @@ export(init) export(list_lookup_parameters) export(read_ext_file) export(read_model) +export(read_nmmodel) export(remove_parameter_from_lookup) export(set_metadata_file) export(submit_model_to_sge) diff --git a/R/extendr-wrappers.R b/R/extendr-wrappers.R index 6acc85f0..0e68ff6a 100644 --- a/R/extendr-wrappers.R +++ b/R/extendr-wrappers.R @@ -468,6 +468,18 @@ from_config_relative <- function(path) .Call(wrap__from_config_relative_wrap, pa #' @noRd to_config_relative <- function(path) .Call(wrap__to_config_relative_wrap, path) +#' Gets model object +#' +#' @param path path to mod or ctl file. +#' +#' @return hyperion_nonmem_model S3 object with `model_source` and `run_status` attributes +#' @export +#' +#' @examples \dontrun{ +#' read_nmmodel("model/nonmem/run001.mod") +#' } +read_nmmodel <- function(path) .Call(wrap__read_nmmodel, path) + #' Submits a NONMEM model to SLURM for execution #' #' This function submits a NONMEM model file to a SLURM cluster for execution, diff --git a/man/read_nmmodel.Rd b/man/read_nmmodel.Rd new file mode 100644 index 00000000..ab016d66 --- /dev/null +++ b/man/read_nmmodel.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/extendr-wrappers.R +\name{read_nmmodel} +\alias{read_nmmodel} +\title{Gets model object} +\usage{ +read_nmmodel(path) +} +\arguments{ +\item{path}{path to mod or ctl file.} +} +\value{ +hyperion_nonmem_model S3 object with \code{model_source} and \code{run_status} attributes +} +\description{ +Gets model object +} +\examples{ +\dontrun{ +read_nmmodel("model/nonmem/run001.mod") +} +} diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index a686ac77..6fa14ad8 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -313,6 +313,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.5" @@ -448,6 +454,7 @@ dependencies = [ "extendr-api", "fs-err", "hyperion-core", + "hyperion-nmparser", "hyperion-nonmem", "hyperion-scheduler", "serde", @@ -463,6 +470,21 @@ dependencies = [ "extendr-api", ] +[[package]] +name = "hyperion-nmparser" +version = "0.1.0" +dependencies = [ + "config", + "extendr-api", + "fs-err", + "hyperion-core", + "insta", + "nonmem", + "nonmem-parser", + "serde", + "tempfile", +] + [[package]] name = "hyperion-nonmem" version = "0.1.0" @@ -654,6 +676,38 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "logos" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2c55a318a87600ea870ff8c2012148b44bf18b74fad48d0f835c38c7d07c5f" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-codegen" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58b3ffaa284e1350d017a57d04ada118c4583cf260c8fb01e0fe28a2e9cf8970" +dependencies = [ + "fnv", + "proc-macro2", + "quote", + "regex-automata", + "regex-syntax", + "syn", +] + +[[package]] +name = "logos-derive" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d3a9855747c17eaf4383823f135220716ab49bea5fbea7dd42cc9a92f8aa31" +dependencies = [ + "logos-codegen", +] + [[package]] name = "memchr" version = "2.8.0" @@ -686,6 +740,18 @@ dependencies = [ "walkdir", ] +[[package]] +name = "nonmem-parser" +version = "0.1.0" +dependencies = [ + "anyhow", + "fs-err", + "logos", + "rand 0.9.2", + "regex", + "serde", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1260,9 +1326,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.13.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a559e63b5d8004e12f9bce88af5c6d939c58de839b7532cfe9653846cedd2a9e" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-xid" diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index aa57b0b5..c76d365c 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -14,13 +14,14 @@ name = 'document' path = 'document.rs' [workspace] -members = ["nonmem", "scheduler", "core"] +members = ["nonmem", "scheduler", "core", "nmparser"] [dependencies] # Internal crates hyperion-nonmem = { path = "nonmem" } hyperion-scheduler = { path = "scheduler" } hyperion-core = { path = "core" } +hyperion-nmparser = { path = "nmparser" } # R Integration extendr-api = { workspace = true } @@ -38,6 +39,7 @@ serde = { workspace = true } extendr-api = { git = "https://github.com/extendr/extendr", branch = "main", features = ["serde"] } # Pharos components +nmparser = { path = "../../../pharos/components/nonmem-parser", package = "nonmem-parser" } nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "main" } config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "main" } scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "main" } diff --git a/src/rust/lib.rs b/src/rust/lib.rs index 304d57a5..6ab22696 100644 --- a/src/rust/lib.rs +++ b/src/rust/lib.rs @@ -11,5 +11,6 @@ extendr_module! { use init; use hyperion_core; use hyperion_nonmem; + use hyperion_nmparser; use hyperion_scheduler; } diff --git a/src/rust/nmparser/Cargo.toml b/src/rust/nmparser/Cargo.toml new file mode 100644 index 00000000..9566331c --- /dev/null +++ b/src/rust/nmparser/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "hyperion-nmparser" +version = "0.1.0" +edition = "2024" + +[dependencies] +# R Integration (needed for extendr macros in modules) +extendr-api = { workspace = true } +hyperion-core = { path = "../core" } + +# Pharos components +nmparser = {workspace = true} +nonmem = { workspace = true } +config = { workspace = true } + +# Core utilities +fs-err = { workspace = true } +serde = { workspace = true } + +# Testing and utilities +insta = { workspace = true } +tempfile = { workspace = true } diff --git a/src/rust/nmparser/src/lib.rs b/src/rust/nmparser/src/lib.rs new file mode 100644 index 00000000..a7d751d4 --- /dev/null +++ b/src/rust/nmparser/src/lib.rs @@ -0,0 +1,10 @@ +use extendr_api::prelude::*; + +pub mod nmmodel; +pub mod utils; + +// Generate extendr module for R integration +extendr_module! { + mod hyperion_nmparser; + use nmmodel; +} diff --git a/src/rust/nmparser/src/nmmodel/mod.rs b/src/rust/nmparser/src/nmmodel/mod.rs new file mode 100644 index 00000000..7aa64b10 --- /dev/null +++ b/src/rust/nmparser/src/nmmodel/mod.rs @@ -0,0 +1,98 @@ +use extendr_api::Result; +use extendr_api::deserializer::from_robj; +use extendr_api::prelude::*; +use extendr_api::serializer::to_robj; + +use fs_err as fs; +use std::path::Path; + +//pharos nonmem crate +use nmparser::Model; + +use crate::utils::{to_config_relative, validate_model_path}; +use hyperion_core::ResultExt; + +/// Helper to convert Model to Robj for read_model and read_model_from_lst +/// +/// This handles comment parsing and Model -> Robj + S3 setting +fn model_to_robj(model: &mut Model, path: impl AsRef) -> Result { + let path = path.as_ref(); + + let mut model_robj = to_robj(&model).map_to_extendr_err("failed to create Robj from Model")?; + + add_filename_attr(&mut model_robj, path)?; + add_model_source_attr(&mut model_robj, path)?; + add_run_status_attr(&mut model_robj, path)?; + + set_model_class(&mut model_robj) +} + +fn add_filename_attr(model_robj: &mut Robj, path: &Path) -> Result<()> { + if let Some(n) = path.file_stem().and_then(|name| name.to_str()) { + model_robj + .set_attrib("filename", n.into_robj()) + .map_to_extendr_err("Failed to set filename attribute")?; + } + Ok(()) +} + +fn add_model_source_attr(model_robj: &mut Robj, path: &Path) -> Result<()> { + let source_path = to_config_relative(path)?; + model_robj + .set_attrib("model_source", source_path.into_robj()) + .map_to_extendr_err("Failed to set model source attribute")?; + + Ok(()) +} + +fn add_run_status_attr(model_robj: &mut Robj, path: &Path) -> Result<()> { + if let Some(ext) = path.extension().and_then(|e| e.to_str()) + && (ext == "mod" || ext == "ctl" || ext == "lst") + { + let run_status = "run"; + model_robj + .set_attrib("run_status", run_status.to_string().into_robj()) + .map_to_extendr_err("Failed to set run_status attribute")?; + } + + Ok(()) +} + +fn set_model_class(model_robj: &mut Robj) -> Result { + let result = model_robj + .set_class(["hyperion_nonmem_nmmodel"]) + .map_to_extendr_err("Failed to set class")?; + + Ok(result.to_owned()) +} + +/// Helper function to reconstruct a pharos Model from hyperion_nonmem_model Robj +pub fn robj_to_model(model: &Robj) -> Result { + from_robj(model).map_to_extendr_err("Failed to create Model from Robj") +} + +/// Gets model object +/// +/// @param path path to mod or ctl file. +/// +/// @return hyperion_nonmem_model S3 object with `model_source` and `run_status` attributes +/// @export +/// +/// @examples \dontrun{ +/// read_nmmodel("model/nonmem/run001.mod") +/// } +#[extendr] +pub fn read_nmmodel(path: &str) -> Result { + let mod_path = validate_model_path(path)?; + let content = fs::read_to_string(&mod_path).map_to_extendr_err("")?; + + let (mut model, _diagonstics) = + Model::parse(&content).map_to_extendr_err("Failed to read model file")?; + let robj_model = model_to_robj(&mut model, mod_path)?; + Ok(robj_model) +} + +extendr_module! { + mod nmmodel; + fn read_nmmodel; +} diff --git a/src/rust/nmparser/src/utils.rs b/src/rust/nmparser/src/utils.rs new file mode 100644 index 00000000..87555968 --- /dev/null +++ b/src/rust/nmparser/src/utils.rs @@ -0,0 +1,462 @@ +use extendr_api::Result; +use extendr_api::prelude::*; + +use fs_err as fs; +use std::path::Component; +use std::path::{Path, PathBuf}; + +// pharos config and nonmem crate +use config::{CONFIG_FILENAME, CommentType, Config, NonmemConfig}; +use nmparser::Model; + +// hyperion core +use hyperion_core::{OptionExt, ResultExt, extendr_err, find_config_dir}; + +/// Finds the correct output file path with the specified extension +/// +/// This function handles various input formats and locates the expected +/// NONMEM output file location following the standard directory structure. +/// +/// # Examples: +/// ``` +/// # use hyperion_nonmem::utils::find_output_file; +/// // Directory input returns expected path +/// let result = find_output_file("models/run001", "ext"); +/// // Should return "models/run001/run001.ext" +/// +/// // .mod file input returns expected path +/// let result = find_output_file("models/run001.mod", "ext"); +/// // Should return "models/run001/run001.ext" +/// ``` +/// +/// # Arguments: +/// * `input_path` - The input path (directory, .mod file, or output file) +/// * `extension` - The desired file extension (without dot, e.g. "ext", "grd") +/// +/// # Returns: +/// * `Ok(PathBuf)` - The path to the output file +/// * `Err(Error)` - If the output file doesn't exist +pub fn find_output_file(input_path: impl AsRef, extension: &str) -> Result { + let path = input_path.as_ref(); + + // If the input already has the target extension, check if it exists + if let Some(current_ext) = path.extension() + && current_ext == extension + { + if path.exists() { + return Ok(path.to_path_buf()); + } else { + return Err(extendr_err!("File not found: {}", path.display())); + } + } + // Determine the base name for the output file + let basename = if path.is_dir() { + // Directory input: use directory name as basename + path.file_name() + .ok_or_extendr_err("Cannot determine directory name")? + .to_string_lossy() + .to_string() + } else { + // File input: use file stem as basename, handling special cases + let stem = path + .file_stem() + .ok_or_extendr_err("Cannot determine file stem")? + .to_string_lossy(); + + // Handle metadata files: run001_metadata.json -> run001 + if stem.ends_with("_metadata") { + stem.strip_suffix("_metadata").unwrap().to_string() + } else { + stem.to_string() + } + }; + + // Construct the expected output file path + let output_path = if path.is_dir() { + // Directory/basename/basename.ext + path.join(format!("{}.{}", basename, extension)) + } else { + // parent/basename/basename.ext + let parent = path + .parent() + .ok_or_extendr_err("Cannot determine parent directory")?; + parent + .join(&basename) + .join(format!("{}.{}", basename, extension)) + }; + + // Verify the output file exists + if output_path.exists() { + Ok(output_path) + } else { + Err(extendr_err!( + "Output file not found: {}\nExpected location based on input: {}", + output_path.display(), + path.display() + )) + } +} + +/// Validate and resolve a model input path (.mod or .ctl). +/// Returns error if path is not a .mod/.ctl file or doesn't exist. +pub fn validate_model_path(input_path: impl AsRef) -> Result { + let path = input_path.as_ref(); + + if path.is_dir() { + return Err(extendr_err!( + "Expected .mod or .ctl file path, got directory: {}", + path.display() + )); + } + + let ext = match path.extension().and_then(|e| e.to_str()) { + Some("mod") => "mod", + Some("ctl") => "ctl", + _ => { + return Err(extendr_err!( + "Expected .mod or .ctl file path: {}", + path.display() + )); + } + }; + + if path.exists() { + let stem = path + .file_stem() + .ok_or_extendr_err("Could not determine model file stem")? + .to_string_lossy() + .to_string(); + if let Some(parent) = path.parent() + && parent + .file_name() + .and_then(|name| name.to_str()) + .is_some_and(|name| name == stem.as_str()) + { + let candidate = parent.with_extension(ext); + return Err(extendr_err!( + "Expected input model file, got output model file: {}\n\ + Try: {}", + path.display(), + candidate.display() + )); + } + + return Ok(path.to_path_buf()); + } + + Err(extendr_err!("File not found: {}", path.display())) +} + +/// Convert an absolute path to be relative to the pharos config directory. +/// Returns the original path if no config directory is found. +pub fn to_config_relative(path: impl AsRef) -> Result { + let path = path.as_ref(); + let config_dir = find_config_dir().map_to_extendr_err("Failed to find config dir")?; + + if let Some(dir) = config_dir { + let rel = make_relative_path(&dir, path); + return Ok(rel.to_string_lossy().to_string()); + } + + Ok(path.to_string_lossy().to_string()) +} + +/// Convert a config-relative path to an absolute path. +/// If the path is already absolute, returns it unchanged. +pub fn from_config_relative(source: impl AsRef) -> Result { + let source_path = source.as_ref(); + if source_path.is_absolute() { + return Ok(source_path.to_path_buf()); + } + + if let Some(dir) = find_config_dir().map_to_extendr_err("Failed to find config dir")? { + return Ok(dir.join(source_path)); + } + + Ok(source_path.to_path_buf()) +} + +/// Extract model_source attribute from Robj and resolve to absolute path. +fn extract_model_source(model: &Robj) -> Result { + let source = model + .get_attrib("model_source") + .ok_or_extendr_err("Model object is missing model_source attribute")?; + let source_str = source + .as_str() + .ok_or_extendr_err("model_source attribute must be a string")?; + from_config_relative(source_str) +} + +/// Extract a path from an Robj (string path or hyperion_nonmem_model object). +/// +/// If `validate_model` is true, validates the path is an existing .mod/.ctl file. +pub fn path_from_robj(input: &Robj, validate_model: bool) -> Result { + let path = if input.inherits("hyperion_nonmem_model") { + extract_model_source(input)? + } else if let Some(s) = input.as_str() { + PathBuf::from(s) + } else { + return Err(extendr_err!( + "Input must be a path or a hyperion_nonmem_model object" + )); + }; + + if validate_model { + validate_model_path(path) + } else { + Ok(path) + } +} + +fn make_relative_path(base: &Path, target: &Path) -> PathBuf { + let base_components: Vec> = base.components().collect(); + let target_components: Vec> = target.components().collect(); + + if base_components.first() != target_components.first() { + return target.to_path_buf(); + } + + let mut idx = 0; + let max = base_components.len().min(target_components.len()); + while idx < max && base_components[idx] == target_components[idx] { + idx += 1; + } + + let mut rel = PathBuf::new(); + for _ in idx..base_components.len() { + rel.push(".."); + } + for comp in target_components.iter().skip(idx) { + rel.push(comp.as_os_str()); + } + + rel +} + +/// Gives Some(Model) if model path is found +pub fn try_parse_model(path: &str) -> Option { + let path_buf = std::path::Path::new(path); + + // If a non-model file is provided (e.g., .grd), search from its parent dir. + let search_path = if path_buf.is_file() { + match path_buf.extension().and_then(|e| e.to_str()) { + Some("mod") | Some("ctl") => path_buf, + _ => path_buf.parent().unwrap_or(path_buf), + } + } else { + path_buf + }; + + let model_path = find_output_file(search_path, "mod") + .or_else(|_| find_output_file(path, "ctl")) + .ok()?; + let content = fs::read_to_string(model_path).ok()?; + let (model, _diagonstics) = Model::parse(&content).ok()?; + Some(model) +} + +/// Gets the comment type from pharos.toml configuration +/// +/// @return Option from pharos config, None if not found or config doesn't exist +pub fn get_comment_type() -> Option { + find_config_dir() + .ok() + .flatten() + .map(|dir| dir.join(CONFIG_FILENAME)) + .and_then(|path| Config::load(path).ok()) + .and_then(|config| config.nonmem.as_ref().and_then(|n| n.comments.r#type)) +} + +pub fn load_nonmem_config(run_nonmem_version: Option<&str>) -> Result<(PathBuf, NonmemConfig)> { + let p = if let Some(root_dir) = + find_config_dir().map_to_extendr_err("Failed to find config dir")? + { + root_dir.join(CONFIG_FILENAME) + } else { + std::env::current_dir() + .map_to_extendr_err("Failed to get current directory")? + .join(CONFIG_FILENAME) + }; + + if !p.exists() { + return Err(extendr_err!( + "pharos config file not found in current or parent directories", + )); + } + + let config = Config::load(&p).map_to_extendr_err("Failed to load config")?; + + let nonmem_config = config + .nonmem + .ok_or_extendr_err("pharos config file does not contain nonmem configuration")?; + + if let Some(version) = run_nonmem_version + && !nonmem_config.versions.contains_key(version) + { + return Err(extendr_err!( + "nonmem version {version} not found in config file" + )); + } + + Ok((p, nonmem_config)) +} + +#[cfg(test)] +mod tests { + use super::*; + use insta::glob; + use std::fs; + use tempfile::TempDir; + + #[test] + fn test_find_output_file_directory_input() { + let temp_dir = TempDir::new().unwrap(); + let run_dir = temp_dir.path().join("run001"); + fs::create_dir(&run_dir).unwrap(); + + let ext_file = run_dir.join("run001.ext"); + fs::write(&ext_file, "test content").unwrap(); + + let result = find_output_file(&run_dir, "ext").unwrap(); + assert_eq!(result, ext_file); + } + + #[test] + fn test_find_output_file_mod_input() { + let temp_dir = TempDir::new().unwrap(); + let mod_file = temp_dir.path().join("run001.mod"); + fs::write(&mod_file, "test content").unwrap(); + + let run_dir = temp_dir.path().join("run001"); + fs::create_dir(&run_dir).unwrap(); + + let ext_file = run_dir.join("run001.ext"); + fs::write(&ext_file, "test content").unwrap(); + + let result = find_output_file(&mod_file, "ext").unwrap(); + assert_eq!(result, ext_file); + } + + #[test] + fn test_find_output_file_already_correct() { + let temp_dir = TempDir::new().unwrap(); + let ext_file = temp_dir.path().join("run001.ext"); + fs::write(&ext_file, "test content").unwrap(); + + let result = find_output_file(&ext_file, "ext").unwrap(); + assert_eq!(result, ext_file); + } + + #[test] + fn test_find_output_file_metadata_input() { + let temp_dir = TempDir::new().unwrap(); + let metadata_file = temp_dir.path().join("run001_metadata.json"); + fs::write(&metadata_file, "{}").unwrap(); + + let run_dir = temp_dir.path().join("run001"); + fs::create_dir(&run_dir).unwrap(); + + let ext_file = run_dir.join("run001.ext"); + fs::write(&ext_file, "test content").unwrap(); + + let result = find_output_file(&metadata_file, "ext").unwrap(); + assert_eq!(result, ext_file); + } + + #[test] + fn test_find_output_file_not_found() { + let temp_dir = TempDir::new().unwrap(); + let run_dir = temp_dir.path().join("run001"); + + let result = find_output_file(&run_dir, "ext"); + assert!(result.is_err()); + } + + #[test] + fn test_try_parse_model_success() { + // Use real test data instead of creating temporary files + let test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test_data"); + glob!(test_dir, "**/*.mod", |path| { + let result = try_parse_model(path.to_str().unwrap()); + assert!( + result.is_some(), + "Expected Some(Model) when valid mod file exists in test data" + ); + }) + } + + #[test] + fn test_try_parse_model_success_for_output_file() { + let test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test_data"); + glob!(test_dir, "**/*.grd", |path| { + // Skip directories where file stem doesn't match directory name + if path.to_string_lossy().contains("run001-running") { + return; + } + let result = try_parse_model(path.to_str().unwrap()); + assert!( + result.is_some(), + "Expected Some(Model) when valid mod file exists in test data" + ); + }) + } + + #[test] + fn test_try_parse_model_no_mod_file() { + let temp_dir = TempDir::new().unwrap(); + let run_dir = temp_dir.path().join("run001"); + fs::create_dir(&run_dir).unwrap(); + + // Don't create a mod file - should return None + let result = try_parse_model(run_dir.to_str().unwrap()); + assert!( + result.is_none(), + "Expected None when mod file doesn't exist" + ); + } + + #[test] + fn test_validate_model_path_ok() { + let temp_dir = TempDir::new().unwrap(); + let mod_file = temp_dir.path().join("run001.mod"); + fs::write(&mod_file, "test content").unwrap(); + + let result = validate_model_path(&mod_file).unwrap(); + assert_eq!(result, mod_file); + } + + #[test] + fn test_validate_model_path_rejects_output_model() { + let temp_dir = TempDir::new().unwrap(); + let run_dir = temp_dir.path().join("run001"); + fs::create_dir(&run_dir).unwrap(); + let output_mod = run_dir.join("run001.mod"); + fs::write(&output_mod, "test content").unwrap(); + + let err = validate_model_path(&output_mod).unwrap_err(); + let message = format!("{err}"); + assert!(message.contains("Expected input model file")); + assert!(message.contains("Try:")); + } + + #[test] + fn test_validate_model_path_rejects_wrong_extension() { + let temp_dir = TempDir::new().unwrap(); + let txt_file = temp_dir.path().join("run001.txt"); + fs::write(&txt_file, "test content").unwrap(); + + let err = validate_model_path(&txt_file).unwrap_err(); + let message = format!("{err}"); + assert!(message.contains("Expected .mod or .ctl")); + } + + #[test] + fn test_from_config_relative_absolute() { + let temp_dir = TempDir::new().unwrap(); + let mod_file = temp_dir.path().join("run001.mod"); + fs::write(&mod_file, "test content").unwrap(); + + let result = from_config_relative(mod_file.to_string_lossy().as_ref()).unwrap(); + assert_eq!(result, mod_file); + } +} From ed03605cbcfd53eb795283ed9719491e0b070a91 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Thu, 26 Mar 2026 15:27:03 -0400 Subject: [PATCH 02/41] update pharos dep --- src/rust/Cargo.lock | 1 + src/rust/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 6fa14ad8..b225d1df 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -743,6 +743,7 @@ dependencies = [ [[package]] name = "nonmem-parser" version = "0.1.0" +source = "git+https://github.com/a2-ai/pharos?branch=nonmem-parser2#58dcbcf15bedc7feb0cb12d5f50a252bf745c4e9" dependencies = [ "anyhow", "fs-err", diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index c76d365c..af4530e7 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -39,7 +39,7 @@ serde = { workspace = true } extendr-api = { git = "https://github.com/extendr/extendr", branch = "main", features = ["serde"] } # Pharos components -nmparser = { path = "../../../pharos/components/nonmem-parser", package = "nonmem-parser" } +nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", branch = "nonmem-parser2" } nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "main" } config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "main" } scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "main" } From c8ba02f1a7a4c5b9daf59d938f007c6a3bb52a21 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Fri, 27 Mar 2026 09:19:04 -0400 Subject: [PATCH 03/41] update mod with data for testing --- inst/extdata/data/derived/PK_Oral_Ex1.csv | 801 ++++++++++++++++++++++ inst/extdata/models/1002.mod | 0 2 files changed, 801 insertions(+) create mode 100644 inst/extdata/data/derived/PK_Oral_Ex1.csv create mode 100644 inst/extdata/models/1002.mod diff --git a/inst/extdata/data/derived/PK_Oral_Ex1.csv b/inst/extdata/data/derived/PK_Oral_Ex1.csv new file mode 100644 index 00000000..d92045f8 --- /dev/null +++ b/inst/extdata/data/derived/PK_Oral_Ex1.csv @@ -0,0 +1,801 @@ +LINE,ID,STUDY,ATFD,NTFD,ATLD,NTLD,DAY,NDAY,AMT,RATE,DUR,CMT,EVID,DVID,ODV,LDV,MDV,BLQ,LLOQ,ROUTE,FREQ,DOSEA,DOSEN,COHORT,PTYPE,SEXF,RACE,AGEBL,AGECBL,WTBL,AEGFRBL,RFCBL,EXCFL +1,1,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,1,19,5,69.2,63,2,1 +2,1,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,1,19,5,69.2,63,2,0 +3,1,1,0.251,0.25,0.251,0.251,1,1,.,.,.,2,0,1,135.44523700374424,4.909,0,0,5,1,1,50,50,1,0,1,1,19,5,69.2,63,2,0 +4,1,1,0.539,0.5,0.539,0.539,1,1,.,.,.,2,0,1,162.78038364546296,5.092,0,0,5,1,1,50,50,1,0,1,1,19,5,69.2,63,2,0 +5,1,1,1.09,1,1.09,1.09,1,1,.,.,.,2,0,1,274.2336868297963,5.614,0,0,5,1,1,50,50,1,0,1,1,19,5,69.2,63,2,0 +6,1,1,4.04,4,4.04,4.04,1,1,.,.,.,2,0,1,157.9651268783753,5.062,0,0,5,1,1,50,50,1,0,1,1,19,5,69.2,63,2,0 +7,1,1,7.909,8,7.909,7.909,1,1,.,.,.,2,0,1,91.09107169399743,4.512,0,0,5,1,1,50,50,1,0,1,1,19,5,69.2,63,2,0 +8,1,1,12.825,12,12.825,12.825,1,1,.,.,.,2,0,1,31.48919877248643,3.45,0,0,5,1,1,50,50,1,0,1,1,19,5,69.2,63,2,0 +9,1,1,22.626,24,22.626,22.626,1,1,.,.,.,2,0,1,15.001380110256893,2.708,0,0,5,1,1,50,50,1,0,1,1,19,5,69.2,63,2,0 +10,1,1,44.077,48,44.077,44.077,2,2,.,.,.,2,0,1,.,.,1,1,5,1,1,50,50,1,0,1,1,19,5,69.2,63,2,0 +11,2,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,3,56,5,79.1,83,2,1 +12,2,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,3,56,5,79.1,83,2,0 +13,2,1,0.265,0.25,0.265,0.265,1,1,.,.,.,2,0,1,97.45319599630928,4.579,0,0,5,1,1,50,50,1,0,1,3,56,5,79.1,83,2,0 +14,2,1,0.451,0.5,0.451,0.451,1,1,.,.,.,2,0,1,230.12964181841207,5.439,0,0,5,1,1,50,50,1,0,1,3,56,5,79.1,83,2,0 +15,2,1,1.035,1,1.035,1.035,1,1,.,.,.,2,0,1,215.75295306533903,5.374,0,0,5,1,1,50,50,1,0,1,3,56,5,79.1,83,2,0 +16,2,1,4.205,4,4.205,4.205,1,1,.,.,.,2,0,1,154.79124313148202,5.042,0,0,5,1,1,50,50,1,0,1,3,56,5,79.1,83,2,0 +17,2,1,8.191,8,8.191,8.191,1,1,.,.,.,2,0,1,106.92951683139773,4.672,0,0,5,1,1,50,50,1,0,1,3,56,5,79.1,83,2,0 +18,2,1,12.748,12,12.748,12.748,1,1,.,.,.,2,0,1,59.571617536167984,4.087,0,0,5,1,1,50,50,1,0,1,3,56,5,79.1,83,2,0 +19,2,1,23.199,24,23.199,23.199,1,1,.,.,.,2,0,1,33.06851120891613,3.499,0,0,5,1,1,50,50,1,0,1,3,56,5,79.1,83,2,0 +20,2,1,44.661,48,44.661,44.661,2,2,.,.,.,2,0,1,11.271104856275693,2.422,0,0,5,1,1,50,50,1,0,1,3,56,5,79.1,83,2,0 +21,3,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,0,1,33,5,110.7,94,1,1 +22,3,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,0,1,33,5,110.7,94,1,0 +23,3,1,0.24,0.25,0.24,0.24,1,1,.,.,.,2,0,1,24.62353109148283,3.204,0,0,5,1,1,50,50,1,0,0,1,33,5,110.7,94,1,0 +24,3,1,0.528,0.5,0.528,0.528,1,1,.,.,.,2,0,1,67.48431907187395,4.212,0,0,5,1,1,50,50,1,0,0,1,33,5,110.7,94,1,0 +25,3,1,1.056,1,1.056,1.056,1,1,.,.,.,2,0,1,72.18967862300104,4.279,0,0,5,1,1,50,50,1,0,0,1,33,5,110.7,94,1,0 +26,3,1,4.348,4,4.348,4.348,1,1,.,.,.,2,0,1,92.36704283920629,4.526,0,0,5,1,1,50,50,1,0,0,1,33,5,110.7,94,1,0 +27,3,1,8.698,8,8.698,8.698,1,1,.,.,.,2,0,1,43.05766164591468,3.763,0,0,5,1,1,50,50,1,0,0,1,33,5,110.7,94,1,0 +28,3,1,11.043,12,11.043,11.043,1,1,.,.,.,2,0,1,21.83841793578263,3.084,0,0,5,1,1,50,50,1,0,0,1,33,5,110.7,94,1,0 +29,3,1,21.666,24,21.666,21.666,1,1,.,.,.,2,0,1,28.91614667097124,3.364,0,0,5,1,1,50,50,1,0,0,1,33,5,110.7,94,1,0 +30,3,1,47.003,48,47.003,47.003,2,2,.,.,.,2,0,1,.,.,1,1,5,1,1,50,50,1,0,0,1,33,5,110.7,94,1,0 +31,4,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,3,60,5,41.9,97,1,1 +32,4,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,3,60,5,41.9,97,1,0 +33,4,1,0.228,0.25,0.228,0.228,1,1,.,.,.,2,0,1,169.6951731610887,5.134,0,0,5,1,1,50,50,1,0,1,3,60,5,41.9,97,1,0 +34,4,1,0.527,0.5,0.527,0.527,1,1,.,.,.,2,0,1,258.225982782318,5.554,0,0,5,1,1,50,50,1,0,1,3,60,5,41.9,97,1,0 +35,4,1,0.931,1,0.931,0.931,1,1,.,.,.,2,0,1,343.8054555025024,5.84,0,0,5,1,1,50,50,1,0,1,3,60,5,41.9,97,1,0 +36,4,1,3.967,4,3.967,3.967,1,1,.,.,.,2,0,1,264.2703488178338,5.577,0,0,5,1,1,50,50,1,0,1,3,60,5,41.9,97,1,0 +37,4,1,7.765,8,7.765,7.765,1,1,.,.,.,2,0,1,193.71969904602912,5.266,0,0,5,1,1,50,50,1,0,1,3,60,5,41.9,97,1,0 +38,4,1,11.455,12,11.455,11.455,1,1,.,.,.,2,0,1,87.72225703058743,4.474,0,0,5,1,1,50,50,1,0,1,3,60,5,41.9,97,1,0 +39,4,1,23.701,24,23.701,23.701,1,1,.,.,.,2,0,1,39.921108893605684,3.687,0,0,5,1,1,50,50,1,0,1,3,60,5,41.9,97,1,0 +40,4,1,44.281,48,44.281,44.281,2,2,.,.,.,2,0,1,6.77985374977073,1.914,0,0,5,1,1,50,50,1,0,1,3,60,5,41.9,97,1,0 +41,5,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,0,1,39,5,47.5,115,1,1 +42,5,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,0,1,39,5,47.5,115,1,0 +43,5,1,0.255,0.25,0.255,0.255,1,1,.,.,.,2,0,1,249.53279459910286,5.52,0,0,5,1,1,50,50,1,0,0,1,39,5,47.5,115,1,0 +44,5,1,0.547,0.5,0.547,0.547,1,1,.,.,.,2,0,1,293.9070261376997,5.683,0,0,5,1,1,50,50,1,0,0,1,39,5,47.5,115,1,0 +45,5,1,1.035,1,1.035,1.035,1,1,.,.,.,2,0,1,490.22226620541164,6.195,0,0,5,1,1,50,50,1,0,0,1,39,5,47.5,115,1,0 +46,5,1,3.81,4,3.81,3.81,1,1,.,.,.,2,0,1,202.44756994084622,5.31,0,0,5,1,1,50,50,1,0,0,1,39,5,47.5,115,1,0 +47,5,1,7.399,8,7.399,7.399,1,1,.,.,.,2,0,1,104.68905735087708,4.651,0,0,5,1,1,50,50,1,0,0,1,39,5,47.5,115,1,0 +48,5,1,11.169,12,11.169,11.169,1,1,.,.,.,2,0,1,141.4274290155354,4.952,0,0,5,1,1,50,50,1,0,0,1,39,5,47.5,115,1,0 +49,5,1,21.694,24,21.694,21.694,1,1,.,.,.,2,0,1,60.800265796533985,4.108,0,0,5,1,1,50,50,1,0,0,1,39,5,47.5,115,1,0 +50,5,1,46.444,48,46.444,46.444,2,2,.,.,.,2,0,1,10.692355648504092,2.37,0,0,5,1,1,50,50,1,0,0,1,39,5,47.5,115,1,0 +51,6,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,3,56,5,71.6,75,2,1 +52,6,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,3,56,5,71.6,75,2,0 +53,6,1,0.253,0.25,0.253,0.253,1,1,.,.,.,2,0,1,94.40039793535624,4.548,0,0,5,1,1,50,50,1,0,1,3,56,5,71.6,75,2,0 +54,6,1,0.459,0.5,0.459,0.459,1,1,.,.,.,2,0,1,203.10586802391276,5.314,0,0,5,1,1,50,50,1,0,1,3,56,5,71.6,75,2,0 +55,6,1,1.002,1,1.002,1.002,1,1,.,.,.,2,0,1,318.2833447860227,5.763,0,0,5,1,1,50,50,1,0,1,3,56,5,71.6,75,2,0 +56,6,1,4.203,4,4.203,4.203,1,1,.,.,.,2,0,1,290.60996160859804,5.672,0,0,5,1,1,50,50,1,0,1,3,56,5,71.6,75,2,0 +57,6,1,7.751,8,7.751,7.751,1,1,.,.,.,2,0,1,96.80287654224922,4.573,0,0,5,1,1,50,50,1,0,1,3,56,5,71.6,75,2,0 +58,6,1,12.722,12,12.722,12.722,1,1,.,.,.,2,0,1,46.77188217118976,3.845,0,0,5,1,1,50,50,1,0,1,3,56,5,71.6,75,2,0 +59,6,1,24.606,24,24.606,24.606,2,1,.,.,.,2,0,1,56.46229354509565,4.034,0,0,5,1,1,50,50,1,0,1,3,56,5,71.6,75,2,0 +60,6,1,51.202,48,51.202,51.202,3,2,.,.,.,2,0,1,12.935218528455787,2.56,0,0,5,1,1,50,50,1,0,1,3,56,5,71.6,75,2,0 +61,7,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,3,29,5,66.4,62,2,1 +62,7,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,3,29,5,66.4,62,2,0 +63,7,1,0.262,0.25,0.262,0.262,1,1,.,.,.,2,0,1,135.4737080194625,4.909,0,0,5,1,1,50,50,1,0,1,3,29,5,66.4,62,2,0 +64,7,1,0.499,0.5,0.499,0.499,1,1,.,.,.,2,0,1,184.77808800181336,5.219,0,0,5,1,1,50,50,1,0,1,3,29,5,66.4,62,2,0 +65,7,1,1.019,1,1.019,1.019,1,1,.,.,.,2,0,1,218.6830683928352,5.388,0,0,5,1,1,50,50,1,0,1,3,29,5,66.4,62,2,0 +66,7,1,3.658,4,3.658,3.658,1,1,.,.,.,2,0,1,269.5928245332529,5.597,0,0,5,1,1,50,50,1,0,1,3,29,5,66.4,62,2,0 +67,7,1,8.181,8,8.181,8.181,1,1,.,.,.,2,0,1,154.59442515544734,5.041,0,0,5,1,1,50,50,1,0,1,3,29,5,66.4,62,2,0 +68,7,1,10.963,12,10.963,10.963,1,1,.,.,.,2,0,1,129.4545159104613,4.863,0,0,5,1,1,50,50,1,0,1,3,29,5,66.4,62,2,0 +69,7,1,23.188,24,23.188,23.188,1,1,.,.,.,2,0,1,64.33189339925366,4.164,0,0,5,1,1,50,50,1,0,1,3,29,5,66.4,62,2,0 +70,7,1,44.457,48,44.457,44.457,2,2,.,.,.,2,0,1,18.656472147539514,2.926,0,0,5,1,1,50,50,1,0,1,3,29,5,66.4,62,2,0 +71,8,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,1,49,5,84.7,116,1,1 +72,8,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,1,49,5,84.7,116,1,0 +73,8,1,0.252,0.25,0.252,0.252,1,1,.,.,.,2,0,1,100.28365397178807,4.608,0,0,5,1,1,50,50,1,0,1,1,49,5,84.7,116,1,0 +74,8,1,0.536,0.5,0.536,0.536,1,1,.,.,.,2,0,1,141.05269221146096,4.949,0,0,5,1,1,50,50,1,0,1,1,49,5,84.7,116,1,0 +75,8,1,0.952,1,0.952,0.952,1,1,.,.,.,2,0,1,199.3914846938247,5.295,0,0,5,1,1,50,50,1,0,1,1,49,5,84.7,116,1,0 +76,8,1,4.093,4,4.093,4.093,1,1,.,.,.,2,0,1,227.61449941839328,5.428,0,0,5,1,1,50,50,1,0,1,1,49,5,84.7,116,1,0 +77,8,1,8.18,8,8.18,8.18,1,1,.,.,.,2,0,1,79.16009593111572,4.371,0,0,5,1,1,50,50,1,0,1,1,49,5,84.7,116,1,0 +78,8,1,11.742,12,11.742,11.742,1,1,.,.,.,2,0,1,65.32755678251588,4.179,0,0,5,1,1,50,50,1,0,1,1,49,5,84.7,116,1,0 +79,8,1,25.634,24,25.634,25.634,2,1,.,.,.,2,0,1,27.734669316625514,3.323,0,0,5,1,1,50,50,1,0,1,1,49,5,84.7,116,1,0 +80,8,1,46.703,48,46.703,46.703,2,2,.,.,.,2,0,1,6.5804820648334665,1.884,0,0,5,1,1,50,50,1,0,1,1,49,5,84.7,116,1,0 +81,9,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,3,26,5,88.1,82,2,1 +82,9,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,3,26,5,88.1,82,2,0 +83,9,1,0.271,0.25,0.271,0.271,1,1,.,.,.,2,0,1,55.92177081314453,4.024,0,0,5,1,1,50,50,1,0,1,3,26,5,88.1,82,2,0 +84,9,1,0.538,0.5,0.538,0.538,1,1,.,.,.,2,0,1,178.77919287456174,5.186,0,0,5,1,1,50,50,1,0,1,3,26,5,88.1,82,2,0 +85,9,1,1.071,1,1.071,1.071,1,1,.,.,.,2,0,1,202.00438237197667,5.308,0,0,5,1,1,50,50,1,0,1,3,26,5,88.1,82,2,0 +86,9,1,3.967,4,3.967,3.967,1,1,.,.,.,2,0,1,173.26650922002892,5.155,0,0,5,1,1,50,50,1,0,1,3,26,5,88.1,82,2,0 +87,9,1,8.777,8,8.777,8.777,1,1,.,.,.,2,0,1,88.45712081437316,4.483,0,0,5,1,1,50,50,1,0,1,3,26,5,88.1,82,2,0 +88,9,1,13.095,12,13.095,13.095,1,1,.,.,.,2,0,1,73.79496848929563,4.301,0,0,5,1,1,50,50,1,0,1,3,26,5,88.1,82,2,0 +89,9,1,23.735,24,23.735,23.735,1,1,.,.,.,2,0,1,62.127232142389644,4.129,0,0,5,1,1,50,50,1,0,1,3,26,5,88.1,82,2,0 +90,9,1,52.001,48,52.001,52.001,3,2,.,.,.,2,0,1,16.85654187006987,2.825,0,0,5,1,1,50,50,1,0,1,3,26,5,88.1,82,2,0 +91,10,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,3,27,5,94.2,95,1,1 +92,10,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,3,27,5,94.2,95,1,0 +93,10,1,0.254,0.25,0.254,0.254,1,1,.,.,.,2,0,1,62.70468207315369,4.138,0,0,5,1,1,50,50,1,0,1,3,27,5,94.2,95,1,0 +94,10,1,0.466,0.5,0.466,0.466,1,1,.,.,.,2,0,1,108.89761745387702,4.69,0,0,5,1,1,50,50,1,0,1,3,27,5,94.2,95,1,0 +95,10,1,1.002,1,1.002,1.002,1,1,.,.,.,2,0,1,185.15065197398172,5.221,0,0,5,1,1,50,50,1,0,1,3,27,5,94.2,95,1,0 +96,10,1,3.928,4,3.928,3.928,1,1,.,.,.,2,0,1,195.6364618026376,5.276,0,0,5,1,1,50,50,1,0,1,3,27,5,94.2,95,1,0 +97,10,1,7.848,8,7.848,7.848,1,1,.,.,.,2,0,1,188.06280516669372,5.237,0,0,5,1,1,50,50,1,0,1,3,27,5,94.2,95,1,0 +98,10,1,11.936,12,11.936,11.936,1,1,.,.,.,2,0,1,82.40375610664775,4.412,0,0,5,1,1,50,50,1,0,1,3,27,5,94.2,95,1,0 +99,10,1,26.052,24,26.052,26.052,2,1,.,.,.,2,0,1,34.7389085775917,3.548,0,0,5,1,1,50,50,1,0,1,3,27,5,94.2,95,1,0 +100,10,1,51.715,48,51.715,51.715,3,2,.,.,.,2,0,1,18.226268866909013,2.903,0,0,5,1,1,50,50,1,0,1,3,27,5,94.2,95,1,0 +101,11,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,0,1,43,5,50.6,109,1,1 +102,11,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,0,1,43,5,50.6,109,1,0 +103,11,1,0.245,0.25,0.245,0.245,1,1,.,.,.,2,0,1,105.19001516194388,4.656,0,0,5,1,1,50,50,1,0,0,1,43,5,50.6,109,1,0 +104,11,1,0.538,0.5,0.538,0.538,1,1,.,.,.,2,0,1,168.28684337711428,5.126,0,0,5,1,1,50,50,1,0,0,1,43,5,50.6,109,1,0 +105,11,1,0.925,1,0.925,0.925,1,1,.,.,.,2,0,1,324.1060870362852,5.781,0,0,5,1,1,50,50,1,0,0,1,43,5,50.6,109,1,0 +106,11,1,4.089,4,4.089,4.089,1,1,.,.,.,2,0,1,231.8231074063792,5.446,0,0,5,1,1,50,50,1,0,0,1,43,5,50.6,109,1,0 +107,11,1,7.507,8,7.507,7.507,1,1,.,.,.,2,0,1,123.41807808522007,4.816,0,0,5,1,1,50,50,1,0,0,1,43,5,50.6,109,1,0 +108,11,1,12.29,12,12.29,12.29,1,1,.,.,.,2,0,1,91.97991358767749,4.522,0,0,5,1,1,50,50,1,0,0,1,43,5,50.6,109,1,0 +109,11,1,25.592,24,25.592,25.592,2,1,.,.,.,2,0,1,22.631899009411562,3.119,0,0,5,1,1,50,50,1,0,0,1,43,5,50.6,109,1,0 +110,11,1,47.142,48,47.142,47.142,2,2,.,.,.,2,0,1,.,.,1,1,5,1,1,50,50,1,0,0,1,43,5,50.6,109,1,0 +111,12,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,3,38,5,74.6,89,2,1 +112,12,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,3,38,5,74.6,89,2,0 +113,12,1,0.272,0.25,0.272,0.272,1,1,.,.,.,2,0,1,89.80210143555645,4.498,0,0,5,1,1,50,50,1,0,1,3,38,5,74.6,89,2,0 +114,12,1,0.479,0.5,0.479,0.479,1,1,.,.,.,2,0,1,149.78718107024633,5.009,0,0,5,1,1,50,50,1,0,1,3,38,5,74.6,89,2,0 +115,12,1,1.074,1,1.074,1.074,1,1,.,.,.,2,0,1,223.0873178541168,5.408,0,0,5,1,1,50,50,1,0,1,3,38,5,74.6,89,2,0 +116,12,1,4.026,4,4.026,4.026,1,1,.,.,.,2,0,1,201.5750956395063,5.306,0,0,5,1,1,50,50,1,0,1,3,38,5,74.6,89,2,0 +117,12,1,7.663,8,7.663,7.663,1,1,.,.,.,2,0,1,99.6120343556312,4.601,0,0,5,1,1,50,50,1,0,1,3,38,5,74.6,89,2,0 +118,12,1,11.966,12,11.966,11.966,1,1,.,.,.,2,0,1,101.20921325003067,4.617,0,0,5,1,1,50,50,1,0,1,3,38,5,74.6,89,2,0 +119,12,1,23.192,24,23.192,23.192,1,1,.,.,.,2,0,1,53.89477012311715,3.987,0,0,5,1,1,50,50,1,0,1,3,38,5,74.6,89,2,0 +120,12,1,52.646,48,52.646,52.646,3,2,.,.,.,2,0,1,6.81006024388014,1.918,0,0,5,1,1,50,50,1,0,1,3,38,5,74.6,89,2,0 +121,13,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,3,40,5,80,73,2,1 +122,13,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,3,40,5,80,73,2,0 +123,13,1,0.245,0.25,0.245,0.245,1,1,.,.,.,2,0,1,83.43894309203456,4.424,0,0,5,1,1,50,50,1,0,1,3,40,5,80,73,2,0 +124,13,1,0.468,0.5,0.468,0.468,1,1,.,.,.,2,0,1,152.993972858459,5.03,0,0,5,1,1,50,50,1,0,1,3,40,5,80,73,2,0 +125,13,1,0.918,1,0.918,0.918,1,1,.,.,.,2,0,1,229.83436240261514,5.437,0,0,5,1,1,50,50,1,0,1,3,40,5,80,73,2,0 +126,13,1,4.159,4,4.159,4.159,1,1,.,.,.,2,0,1,138.69508050143315,4.932,0,0,5,1,1,50,50,1,0,1,3,40,5,80,73,2,0 +127,13,1,8.229,8,8.229,8.229,1,1,.,.,.,2,0,1,105.52481658119962,4.659,0,0,5,1,1,50,50,1,0,1,3,40,5,80,73,2,0 +128,13,1,12.449,12,12.449,12.449,1,1,.,.,.,2,0,1,82.69099954281738,4.415,0,0,5,1,1,50,50,1,0,1,3,40,5,80,73,2,0 +129,13,1,23.564,24,23.564,23.564,1,1,.,.,.,2,0,1,45.429328987267105,3.816,0,0,5,1,1,50,50,1,0,1,3,40,5,80,73,2,0 +130,13,1,52.558,48,52.558,52.558,3,2,.,.,.,2,0,1,16.92462070175668,2.829,0,0,5,1,1,50,50,1,0,1,3,40,5,80,73,2,0 +131,14,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,0,1,55,5,58.3,82,2,1 +132,14,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,0,1,55,5,58.3,82,2,0 +133,14,1,0.259,0.25,0.259,0.259,1,1,.,.,.,2,0,1,85.36653666847653,4.447,0,0,5,1,1,50,50,1,0,0,1,55,5,58.3,82,2,0 +134,14,1,0.463,0.5,0.463,0.463,1,1,.,.,.,2,0,1,82.3398004759733,4.411,0,0,5,1,1,50,50,1,0,0,1,55,5,58.3,82,2,0 +135,14,1,1.04,1,1.04,1.04,1,1,.,.,.,2,0,1,204.46690489883147,5.32,0,0,5,1,1,50,50,1,0,0,1,55,5,58.3,82,2,0 +136,14,1,4.239,4,4.239,4.239,1,1,.,.,.,2,0,1,194.76042695170875,5.272,0,0,5,1,1,50,50,1,0,0,1,55,5,58.3,82,2,0 +137,14,1,7.206,8,7.206,7.206,1,1,.,.,.,2,0,1,142.99341877974857,4.963,0,0,5,1,1,50,50,1,0,0,1,55,5,58.3,82,2,0 +138,14,1,12.395,12,12.395,12.395,1,1,.,.,.,2,0,1,85.56372644655724,4.449,0,0,5,1,1,50,50,1,0,0,1,55,5,58.3,82,2,0 +139,14,1,23.237,24,23.237,23.237,1,1,.,.,.,2,0,1,29.642554300031655,3.389,0,0,5,1,1,50,50,1,0,0,1,55,5,58.3,82,2,0 +140,14,1,45.973,48,45.973,45.973,2,2,.,.,.,2,0,1,6.010796832004558,1.794,0,0,5,1,1,50,50,1,0,0,1,55,5,58.3,82,2,0 +141,15,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,3,59,5,91.9,108,1,1 +142,15,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,3,59,5,91.9,108,1,0 +143,15,1,0.236,0.25,0.236,0.236,1,1,.,.,.,2,0,1,72.2221702863214,4.28,0,0,5,1,1,50,50,1,0,1,3,59,5,91.9,108,1,0 +144,15,1,0.502,0.5,0.502,0.502,1,1,.,.,.,2,0,1,145.8125529365367,4.982,0,0,5,1,1,50,50,1,0,1,3,59,5,91.9,108,1,0 +145,15,1,0.958,1,0.958,0.958,1,1,.,.,.,2,0,1,245.45303661440187,5.503,0,0,5,1,1,50,50,1,0,1,3,59,5,91.9,108,1,0 +146,15,1,3.879,4,3.879,3.879,1,1,.,.,.,2,0,1,189.70425402824299,5.245,0,0,5,1,1,50,50,1,0,1,3,59,5,91.9,108,1,0 +147,15,1,7.258,8,7.258,7.258,1,1,.,.,.,2,0,1,178.6140994407548,5.185,0,0,5,1,1,50,50,1,0,1,3,59,5,91.9,108,1,0 +148,15,1,12.106,12,12.106,12.106,1,1,.,.,.,2,0,1,73.85645867201875,4.302,0,0,5,1,1,50,50,1,0,1,3,59,5,91.9,108,1,0 +149,15,1,23.431,24,23.431,23.431,1,1,.,.,.,2,0,1,58.5182186455088,4.069,0,0,5,1,1,50,50,1,0,1,3,59,5,91.9,108,1,0 +150,15,1,50.025,48,50.025,50.025,3,2,.,.,.,2,0,1,17.587436942518334,2.867,0,0,5,1,1,50,50,1,0,1,3,59,5,91.9,108,1,0 +151,16,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,3,32,5,57.9,106,1,1 +152,16,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,3,32,5,57.9,106,1,0 +153,16,1,0.245,0.25,0.245,0.245,1,1,.,.,.,2,0,1,146.0809549899598,4.984,0,0,5,1,1,50,50,1,0,1,3,32,5,57.9,106,1,0 +154,16,1,0.456,0.5,0.456,0.456,1,1,.,.,.,2,0,1,248.67788229455675,5.516,0,0,5,1,1,50,50,1,0,1,3,32,5,57.9,106,1,0 +155,16,1,1.061,1,1.061,1.061,1,1,.,.,.,2,0,1,356.82911831523467,5.877,0,0,5,1,1,50,50,1,0,1,3,32,5,57.9,106,1,0 +156,16,1,3.706,4,3.706,3.706,1,1,.,.,.,2,0,1,181.47615746102215,5.201,0,0,5,1,1,50,50,1,0,1,3,32,5,57.9,106,1,0 +157,16,1,8.25,8,8.25,8.25,1,1,.,.,.,2,0,1,191.74983411936978,5.256,0,0,5,1,1,50,50,1,0,1,3,32,5,57.9,106,1,0 +158,16,1,12.325,12,12.325,12.325,1,1,.,.,.,2,0,1,152.6125825987005,5.028,0,0,5,1,1,50,50,1,0,1,3,32,5,57.9,106,1,0 +159,16,1,22.16,24,22.16,22.16,1,1,.,.,.,2,0,1,51.689080868684464,3.945,0,0,5,1,1,50,50,1,0,1,3,32,5,57.9,106,1,0 +160,16,1,49.707,48,49.707,49.707,3,2,.,.,.,2,0,1,10.465110466955615,2.348,0,0,5,1,1,50,50,1,0,1,3,32,5,57.9,106,1,0 +161,17,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,0,1,23,5,62.7,119,1,1 +162,17,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,0,1,23,5,62.7,119,1,0 +163,17,1,0.251,0.25,0.251,0.251,1,1,.,.,.,2,0,1,142.03067351163958,4.956,0,0,5,1,1,50,50,1,0,0,1,23,5,62.7,119,1,0 +164,17,1,0.522,0.5,0.522,0.522,1,1,.,.,.,2,0,1,187.9079244478451,5.236,0,0,5,1,1,50,50,1,0,0,1,23,5,62.7,119,1,0 +165,17,1,1.051,1,1.051,1.051,1,1,.,.,.,2,0,1,319.1262156050448,5.766,0,0,5,1,1,50,50,1,0,0,1,23,5,62.7,119,1,0 +166,17,1,3.657,4,3.657,3.657,1,1,.,.,.,2,0,1,172.90508940988434,5.153,0,0,5,1,1,50,50,1,0,0,1,23,5,62.7,119,1,0 +167,17,1,8.164,8,8.164,8.164,1,1,.,.,.,2,0,1,85.9578520260203,4.454,0,0,5,1,1,50,50,1,0,0,1,23,5,62.7,119,1,0 +168,17,1,10.92,12,10.92,10.92,1,1,.,.,.,2,0,1,43.746953996386196,3.778,0,0,5,1,1,50,50,1,0,0,1,23,5,62.7,119,1,0 +169,17,1,24.093,24,24.093,24.093,2,1,.,.,.,2,0,1,24.0419862220063,3.18,0,0,5,1,1,50,50,1,0,0,1,23,5,62.7,119,1,0 +170,17,1,46.256,48,46.256,46.256,2,2,.,.,.,2,0,1,.,.,1,1,5,1,1,50,50,1,0,0,1,23,5,62.7,119,1,0 +171,18,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,0,1,21,5,65.6,88,2,1 +172,18,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,0,1,21,5,65.6,88,2,0 +173,18,1,0.248,0.25,0.248,0.248,1,1,.,.,.,2,0,1,63.547226666011206,4.152,0,0,5,1,1,50,50,1,0,0,1,21,5,65.6,88,2,0 +174,18,1,0.492,0.5,0.492,0.492,1,1,.,.,.,2,0,1,76.10211318642305,4.332,0,0,5,1,1,50,50,1,0,0,1,21,5,65.6,88,2,0 +175,18,1,0.953,1,0.953,0.953,1,1,.,.,.,2,0,1,127.96102253972899,4.852,0,0,5,1,1,50,50,1,0,0,1,21,5,65.6,88,2,0 +176,18,1,3.894,4,3.894,3.894,1,1,.,.,.,2,0,1,146.0356260475136,4.984,0,0,5,1,1,50,50,1,0,0,1,21,5,65.6,88,2,0 +177,18,1,7.975,8,7.975,7.975,1,1,.,.,.,2,0,1,85.07102175609896,4.443,0,0,5,1,1,50,50,1,0,0,1,21,5,65.6,88,2,0 +178,18,1,11.66,12,11.66,11.66,1,1,.,.,.,2,0,1,67.02881309796206,4.205,0,0,5,1,1,50,50,1,0,0,1,21,5,65.6,88,2,0 +179,18,1,22.861,24,22.861,22.861,1,1,.,.,.,2,0,1,19.305289030884524,2.96,0,0,5,1,1,50,50,1,0,0,1,21,5,65.6,88,2,0 +180,18,1,51.323,48,51.323,51.323,3,2,.,.,.,2,0,1,.,.,1,1,5,1,1,50,50,1,0,0,1,21,5,65.6,88,2,0 +181,19,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,0,1,30,5,67.5,78,2,1 +182,19,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,0,1,30,5,67.5,78,2,0 +183,19,1,0.243,0.25,0.243,0.243,1,1,.,.,.,2,0,1,52.32994461808845,3.958,0,0,5,1,1,50,50,1,0,0,1,30,5,67.5,78,2,0 +184,19,1,0.492,0.5,0.492,0.492,1,1,.,.,.,2,0,1,102.23023201300768,4.627,0,0,5,1,1,50,50,1,0,0,1,30,5,67.5,78,2,0 +185,19,1,1.094,1,1.094,1.094,1,1,.,.,.,2,0,1,157.69795914362115,5.061,0,0,5,1,1,50,50,1,0,0,1,30,5,67.5,78,2,0 +186,19,1,4.219,4,4.219,4.219,1,1,.,.,.,2,0,1,204.3837705584912,5.32,0,0,5,1,1,50,50,1,0,0,1,30,5,67.5,78,2,0 +187,19,1,7.474,8,7.474,7.474,1,1,.,.,.,2,0,1,130.6893154068886,4.873,0,0,5,1,1,50,50,1,0,0,1,30,5,67.5,78,2,0 +188,19,1,11.87,12,11.87,11.87,1,1,.,.,.,2,0,1,43.83466017740129,3.78,0,0,5,1,1,50,50,1,0,0,1,30,5,67.5,78,2,0 +189,19,1,23.167,24,23.167,23.167,1,1,.,.,.,2,0,1,49.75309931078222,3.907,0,0,5,1,1,50,50,1,0,0,1,30,5,67.5,78,2,0 +190,19,1,49.134,48,49.134,49.134,3,2,.,.,.,2,0,1,10.912049044214108,2.39,0,0,5,1,1,50,50,1,0,0,1,30,5,67.5,78,2,0 +191,20,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,3,26,5,56.3,115,1,1 +192,20,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,3,26,5,56.3,115,1,0 +193,20,1,0.228,0.25,0.228,0.228,1,1,.,.,.,2,0,1,126.5020318046857,4.84,0,0,5,1,1,50,50,1,0,1,3,26,5,56.3,115,1,0 +194,20,1,0.498,0.5,0.498,0.498,1,1,.,.,.,2,0,1,218.22018597644612,5.386,0,0,5,1,1,50,50,1,0,1,3,26,5,56.3,115,1,0 +195,20,1,0.993,1,0.993,0.993,1,1,.,.,.,2,0,1,267.6697423227123,5.59,0,0,5,1,1,50,50,1,0,1,3,26,5,56.3,115,1,0 +196,20,1,3.853,4,3.853,3.853,1,1,.,.,.,2,0,1,239.16161296190822,5.477,0,0,5,1,1,50,50,1,0,1,3,26,5,56.3,115,1,0 +197,20,1,8.051,8,8.051,8.051,1,1,.,.,.,2,0,1,164.85086385207168,5.105,0,0,5,1,1,50,50,1,0,1,3,26,5,56.3,115,1,0 +198,20,1,11.317,12,11.317,11.317,1,1,.,.,.,2,0,1,121.27169586086534,4.798,0,0,5,1,1,50,50,1,0,1,3,26,5,56.3,115,1,0 +199,20,1,23.219,24,23.219,23.219,1,1,.,.,.,2,0,1,37.72747226980194,3.63,0,0,5,1,1,50,50,1,0,1,3,26,5,56.3,115,1,0 +200,20,1,46.793,48,46.793,46.793,2,2,.,.,.,2,0,1,10.55656806084108,2.357,0,0,5,1,1,50,50,1,0,1,3,26,5,56.3,115,1,0 +201,21,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,1,3,49,5,86,112,1,1 +202,21,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,1,3,49,5,86,112,1,0 +203,21,1,0.253,0.25,0.253,0.253,1,1,.,.,.,2,0,1,222.20388527834575,5.404,0,0,5,1,1,100,100,2,0,1,3,49,5,86,112,1,0 +204,21,1,0.524,0.5,0.524,0.524,1,1,.,.,.,2,0,1,398.25758297732244,5.987,0,0,5,1,1,100,100,2,0,1,3,49,5,86,112,1,0 +205,21,1,0.958,1,0.958,0.958,1,1,.,.,.,2,0,1,518.2403345077166,6.25,0,0,5,1,1,100,100,2,0,1,3,49,5,86,112,1,0 +206,21,1,4.203,4,4.203,4.203,1,1,.,.,.,2,0,1,348.63694073274286,5.854,0,0,5,1,1,100,100,2,0,1,3,49,5,86,112,1,0 +207,21,1,7.365,8,7.365,7.365,1,1,.,.,.,2,0,1,176.74557193919847,5.175,0,0,5,1,1,100,100,2,0,1,3,49,5,86,112,1,0 +208,21,1,12.031,12,12.031,12.031,1,1,.,.,.,2,0,1,160.80803303860978,5.08,0,0,5,1,1,100,100,2,0,1,3,49,5,86,112,1,0 +209,21,1,24.323,24,24.323,24.323,2,1,.,.,.,2,0,1,67.33980871753079,4.21,0,0,5,1,1,100,100,2,0,1,3,49,5,86,112,1,0 +210,21,1,47.503,48,47.503,47.503,2,2,.,.,.,2,0,1,24.282924658464673,3.19,0,0,5,1,1,100,100,2,0,1,3,49,5,86,112,1,0 +211,22,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,0,1,19,5,77.6,88,2,1 +212,22,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,0,1,19,5,77.6,88,2,0 +213,22,1,0.264,0.25,0.264,0.264,1,1,.,.,.,2,0,1,113.56922512962268,4.732,0,0,5,1,1,100,100,2,0,0,1,19,5,77.6,88,2,0 +214,22,1,0.459,0.5,0.459,0.459,1,1,.,.,.,2,0,1,134.64147802024502,4.903,0,0,5,1,1,100,100,2,0,0,1,19,5,77.6,88,2,0 +215,22,1,0.971,1,0.971,0.971,1,1,.,.,.,2,0,1,308.0805834282529,5.73,0,0,5,1,1,100,100,2,0,0,1,19,5,77.6,88,2,0 +216,22,1,3.686,4,3.686,3.686,1,1,.,.,.,2,0,1,245.73193003290982,5.504,0,0,5,1,1,100,100,2,0,0,1,19,5,77.6,88,2,0 +217,22,1,7.327,8,7.327,7.327,1,1,.,.,.,2,0,1,199.30078032618894,5.295,0,0,5,1,1,100,100,2,0,0,1,19,5,77.6,88,2,0 +218,22,1,12.042,12,12.042,12.042,1,1,.,.,.,2,0,1,73.38640214868025,4.296,0,0,5,1,1,100,100,2,0,0,1,19,5,77.6,88,2,0 +219,22,1,21.601,24,21.601,21.601,1,1,.,.,.,2,0,1,39.890315458890676,3.686,0,0,5,1,1,100,100,2,0,0,1,19,5,77.6,88,2,0 +220,22,1,44.514,48,44.514,44.514,2,2,.,.,.,2,0,1,8.13083127863289,2.096,0,0,5,1,1,100,100,2,0,0,1,19,5,77.6,88,2,0 +221,23,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,1,3,54,5,52.7,60,2,1 +222,23,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,1,3,54,5,52.7,60,2,0 +223,23,1,0.254,0.25,0.254,0.254,1,1,.,.,.,2,0,1,186.23930136729774,5.227,0,0,5,1,1,100,100,2,0,1,3,54,5,52.7,60,2,0 +224,23,1,0.518,0.5,0.518,0.518,1,1,.,.,.,2,0,1,510.8848940320924,6.236,0,0,5,1,1,100,100,2,0,1,3,54,5,52.7,60,2,0 +225,23,1,1.077,1,1.077,1.077,1,1,.,.,.,2,0,1,427.1187622139237,6.057,0,0,5,1,1,100,100,2,0,1,3,54,5,52.7,60,2,0 +226,23,1,4.216,4,4.216,4.216,1,1,.,.,.,2,0,1,614.2355913036895,6.42,0,0,5,1,1,100,100,2,0,1,3,54,5,52.7,60,2,0 +227,23,1,7.564,8,7.564,7.564,1,1,.,.,.,2,0,1,561.9300237067965,6.331,0,0,5,1,1,100,100,2,0,1,3,54,5,52.7,60,2,0 +228,23,1,10.834,12,10.834,10.834,1,1,.,.,.,2,0,1,316.43371686943203,5.757,0,0,5,1,1,100,100,2,0,1,3,54,5,52.7,60,2,0 +229,23,1,25.201,24,25.201,25.201,2,1,.,.,.,2,0,1,119.67245706493227,4.785,0,0,5,1,1,100,100,2,0,1,3,54,5,52.7,60,2,0 +230,23,1,51.172,48,51.172,51.172,3,2,.,.,.,2,0,1,26.37359389340642,3.272,0,0,5,1,1,100,100,2,0,1,3,54,5,52.7,60,2,0 +231,24,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,0,1,37,5,75,74,2,1 +232,24,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,0,1,37,5,75,74,2,0 +233,24,1,0.249,0.25,0.249,0.249,1,1,.,.,.,2,0,1,177.10528460730433,5.177,0,0,5,1,1,100,100,2,0,0,1,37,5,75,74,2,0 +234,24,1,0.461,0.5,0.461,0.461,1,1,.,.,.,2,0,1,372.62432730775413,5.921,0,0,5,1,1,100,100,2,0,0,1,37,5,75,74,2,0 +235,24,1,1.037,1,1.037,1.037,1,1,.,.,.,2,0,1,559.2484081413529,6.327,0,0,5,1,1,100,100,2,0,0,1,37,5,75,74,2,0 +236,24,1,3.606,4,3.606,3.606,1,1,.,.,.,2,0,1,369.18356207841725,5.911,0,0,5,1,1,100,100,2,0,0,1,37,5,75,74,2,0 +237,24,1,7.785,8,7.785,7.785,1,1,.,.,.,2,0,1,168.99172047389044,5.13,0,0,5,1,1,100,100,2,0,0,1,37,5,75,74,2,0 +238,24,1,12.774,12,12.774,12.774,1,1,.,.,.,2,0,1,60.90699672033179,4.109,0,0,5,1,1,100,100,2,0,0,1,37,5,75,74,2,0 +239,24,1,21.798,24,21.798,21.798,1,1,.,.,.,2,0,1,66.75234319402962,4.201,0,0,5,1,1,100,100,2,0,0,1,37,5,75,74,2,0 +240,24,1,43.678,48,43.678,43.678,2,2,.,.,.,2,0,1,16.993578396296172,2.833,0,0,5,1,1,100,100,2,0,0,1,37,5,75,74,2,0 +241,25,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,0,1,46,5,65.5,117,1,1 +242,25,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,0,1,46,5,65.5,117,1,0 +243,25,1,0.271,0.25,0.271,0.271,1,1,.,.,.,2,0,1,150.58974977915426,5.015,0,0,5,1,1,100,100,2,0,0,1,46,5,65.5,117,1,0 +244,25,1,0.471,0.5,0.471,0.471,1,1,.,.,.,2,0,1,184.75505229278053,5.219,0,0,5,1,1,100,100,2,0,0,1,46,5,65.5,117,1,0 +245,25,1,0.971,1,0.971,0.971,1,1,.,.,.,2,0,1,463.9218796878544,6.14,0,0,5,1,1,100,100,2,0,0,1,46,5,65.5,117,1,0 +246,25,1,4.309,4,4.309,4.309,1,1,.,.,.,2,0,1,307.5533109805224,5.729,0,0,5,1,1,100,100,2,0,0,1,46,5,65.5,117,1,0 +247,25,1,8.611,8,8.611,8.611,1,1,.,.,.,2,0,1,166.96362286652396,5.118,0,0,5,1,1,100,100,2,0,0,1,46,5,65.5,117,1,0 +248,25,1,11.404,12,11.404,11.404,1,1,.,.,.,2,0,1,90.13967577374842,4.501,0,0,5,1,1,100,100,2,0,0,1,46,5,65.5,117,1,0 +249,25,1,23.08,24,23.08,23.08,1,1,.,.,.,2,0,1,37.07870656317447,3.613,0,0,5,1,1,100,100,2,0,0,1,46,5,65.5,117,1,0 +250,25,1,44.604,48,44.604,44.604,2,2,.,.,.,2,0,1,5.5919126397116825,1.721,0,0,5,1,1,100,100,2,0,0,1,46,5,65.5,117,1,0 +251,26,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,1,3,24,5,68.4,120,1,1 +252,26,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,1,3,24,5,68.4,120,1,0 +253,26,1,0.234,0.25,0.234,0.234,1,1,.,.,.,2,0,1,197.5755036051143,5.286,0,0,5,1,1,100,100,2,0,1,3,24,5,68.4,120,1,0 +254,26,1,0.452,0.5,0.452,0.452,1,1,.,.,.,2,0,1,117.84540397640403,4.769,0,0,5,1,1,100,100,2,0,1,3,24,5,68.4,120,1,0 +255,26,1,1.081,1,1.081,1.081,1,1,.,.,.,2,0,1,324.1776215337058,5.781,0,0,5,1,1,100,100,2,0,1,3,24,5,68.4,120,1,0 +256,26,1,4.145,4,4.145,4.145,1,1,.,.,.,2,0,1,449.5119850783137,6.108,0,0,5,1,1,100,100,2,0,1,3,24,5,68.4,120,1,0 +257,26,1,7.358,8,7.358,7.358,1,1,.,.,.,2,0,1,195.77001223162944,5.277,0,0,5,1,1,100,100,2,0,1,3,24,5,68.4,120,1,0 +258,26,1,10.958,12,10.958,10.958,1,1,.,.,.,2,0,1,211.63561405874458,5.355,0,0,5,1,1,100,100,2,0,1,3,24,5,68.4,120,1,0 +259,26,1,21.703,24,21.703,21.703,1,1,.,.,.,2,0,1,93.45371430621178,4.537,0,0,5,1,1,100,100,2,0,1,3,24,5,68.4,120,1,0 +260,26,1,50.514,48,50.514,50.514,3,2,.,.,.,2,0,1,18.035162119786612,2.892,0,0,5,1,1,100,100,2,0,1,3,24,5,68.4,120,1,0 +261,27,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,0,1,55,5,97,62,2,1 +262,27,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,0,1,55,5,97,62,2,0 +263,27,1,0.236,0.25,0.236,0.236,1,1,.,.,.,2,0,1,137.42754915883893,4.923,0,0,5,1,1,100,100,2,0,0,1,55,5,97,62,2,0 +264,27,1,0.537,0.5,0.537,0.537,1,1,.,.,.,2,0,1,238.43467211915168,5.474,0,0,5,1,1,100,100,2,0,0,1,55,5,97,62,2,0 +265,27,1,1.091,1,1.091,1.091,1,1,.,.,.,2,0,1,340.86594411945345,5.831,0,0,5,1,1,100,100,2,0,0,1,55,5,97,62,2,0 +266,27,1,4.193,4,4.193,4.193,1,1,.,.,.,2,0,1,238.0155245050968,5.472,0,0,5,1,1,100,100,2,0,0,1,55,5,97,62,2,0 +267,27,1,7.607,8,7.607,7.607,1,1,.,.,.,2,0,1,171.26553945487865,5.143,0,0,5,1,1,100,100,2,0,0,1,55,5,97,62,2,0 +268,27,1,10.895,12,10.895,10.895,1,1,.,.,.,2,0,1,171.7692389052334,5.146,0,0,5,1,1,100,100,2,0,0,1,55,5,97,62,2,0 +269,27,1,23.315,24,23.315,23.315,1,1,.,.,.,2,0,1,52.067285558051026,3.953,0,0,5,1,1,100,100,2,0,0,1,55,5,97,62,2,0 +270,27,1,48.017,48,48.017,48.017,3,2,.,.,.,2,0,1,16.375795531208155,2.796,0,0,5,1,1,100,100,2,0,0,1,55,5,97,62,2,0 +271,28,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,0,1,30,5,73.4,107,1,1 +272,28,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,0,1,30,5,73.4,107,1,0 +273,28,1,0.235,0.25,0.235,0.235,1,1,.,.,.,2,0,1,199.78359666315265,5.297,0,0,5,1,1,100,100,2,0,0,1,30,5,73.4,107,1,0 +274,28,1,0.47,0.5,0.47,0.47,1,1,.,.,.,2,0,1,328.899055432316,5.796,0,0,5,1,1,100,100,2,0,0,1,30,5,73.4,107,1,0 +275,28,1,0.988,1,0.988,0.988,1,1,.,.,.,2,0,1,360.6530379935688,5.888,0,0,5,1,1,100,100,2,0,0,1,30,5,73.4,107,1,0 +276,28,1,3.732,4,3.732,3.732,1,1,.,.,.,2,0,1,411.9183759810828,6.021,0,0,5,1,1,100,100,2,0,0,1,30,5,73.4,107,1,0 +277,28,1,7.525,8,7.525,7.525,1,1,.,.,.,2,0,1,240.8647333043881,5.484,0,0,5,1,1,100,100,2,0,0,1,30,5,73.4,107,1,0 +278,28,1,12.949,12,12.949,12.949,1,1,.,.,.,2,0,1,67.11009645588638,4.206,0,0,5,1,1,100,100,2,0,0,1,30,5,73.4,107,1,0 +279,28,1,22.676,24,22.676,22.676,1,1,.,.,.,2,0,1,46.38914180930702,3.837,0,0,5,1,1,100,100,2,0,0,1,30,5,73.4,107,1,0 +280,28,1,48.559,48,48.559,48.559,3,2,.,.,.,2,0,1,5.8506332712917875,1.767,0,0,5,1,1,100,100,2,0,0,1,30,5,73.4,107,1,0 +281,29,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,1,3,58,5,80.2,115,1,1 +282,29,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,1,3,58,5,80.2,115,1,0 +283,29,1,0.264,0.25,0.264,0.264,1,1,.,.,.,2,0,1,160.8814676510257,5.081,0,0,5,1,1,100,100,2,0,1,3,58,5,80.2,115,1,0 +284,29,1,0.473,0.5,0.473,0.473,1,1,.,.,.,2,0,1,404.1915881442617,6.002,0,0,5,1,1,100,100,2,0,1,3,58,5,80.2,115,1,0 +285,29,1,1.027,1,1.027,1.027,1,1,.,.,.,2,0,1,298.33358493377875,5.698,0,0,5,1,1,100,100,2,0,1,3,58,5,80.2,115,1,0 +286,29,1,4.12,4,4.12,4.12,1,1,.,.,.,2,0,1,292.7187971223755,5.679,0,0,5,1,1,100,100,2,0,1,3,58,5,80.2,115,1,0 +287,29,1,8.288,8,8.288,8.288,1,1,.,.,.,2,0,1,137.9153865093006,4.927,0,0,5,1,1,100,100,2,0,1,3,58,5,80.2,115,1,0 +288,29,1,13.072,12,13.072,13.072,1,1,.,.,.,2,0,1,119.17716220360658,4.781,0,0,5,1,1,100,100,2,0,1,3,58,5,80.2,115,1,0 +289,29,1,23.54,24,23.54,23.54,1,1,.,.,.,2,0,1,48.778083236087596,3.887,0,0,5,1,1,100,100,2,0,1,3,58,5,80.2,115,1,0 +290,29,1,43.985,48,43.985,43.985,2,2,.,.,.,2,0,1,11.699459354090898,2.46,0,0,5,1,1,100,100,2,0,1,3,58,5,80.2,115,1,0 +291,30,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,0,1,55,5,49.4,61,2,1 +292,30,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,0,1,55,5,49.4,61,2,0 +293,30,1,0.24,0.25,0.24,0.24,1,1,.,.,.,2,0,1,470.301049600942,6.153,0,0,5,1,1,100,100,2,0,0,1,55,5,49.4,61,2,0 +294,30,1,0.495,0.5,0.495,0.495,1,1,.,.,.,2,0,1,538.7876541360903,6.289,0,0,5,1,1,100,100,2,0,0,1,55,5,49.4,61,2,0 +295,30,1,1.009,1,1.009,1.009,1,1,.,.,.,2,0,1,545.4208017654174,6.302,0,0,5,1,1,100,100,2,0,0,1,55,5,49.4,61,2,0 +296,30,1,3.813,4,3.813,3.813,1,1,.,.,.,2,0,1,407.4062601637788,6.01,0,0,5,1,1,100,100,2,0,0,1,55,5,49.4,61,2,0 +297,30,1,8.245,8,8.245,8.245,1,1,.,.,.,2,0,1,267.5045139677714,5.589,0,0,5,1,1,100,100,2,0,0,1,55,5,49.4,61,2,0 +298,30,1,11.953,12,11.953,11.953,1,1,.,.,.,2,0,1,93.15730482418972,4.534,0,0,5,1,1,100,100,2,0,0,1,55,5,49.4,61,2,0 +299,30,1,25.113,24,25.113,25.113,2,1,.,.,.,2,0,1,64.63172628734895,4.169,0,0,5,1,1,100,100,2,0,0,1,55,5,49.4,61,2,0 +300,30,1,47.699,48,47.699,47.699,2,2,.,.,.,2,0,1,11.546995613104817,2.446,0,0,5,1,1,100,100,2,0,0,1,55,5,49.4,61,2,0 +301,31,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,0,1,25,5,76.7,66,2,1 +302,31,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,0,1,25,5,76.7,66,2,0 +303,31,1,0.227,0.25,0.227,0.227,1,1,.,.,.,2,0,1,156.40350769212318,5.052,0,0,5,1,1,100,100,2,0,0,1,25,5,76.7,66,2,0 +304,31,1,0.516,0.5,0.516,0.516,1,1,.,.,.,2,0,1,159.4846145658938,5.072,0,0,5,1,1,100,100,2,0,0,1,25,5,76.7,66,2,0 +305,31,1,0.976,1,0.976,0.976,1,1,.,.,.,2,0,1,288.5501768992313,5.665,0,0,5,1,1,100,100,2,0,0,1,25,5,76.7,66,2,0 +306,31,1,3.854,4,3.854,3.854,1,1,.,.,.,2,0,1,346.3679858899865,5.848,0,0,5,1,1,100,100,2,0,0,1,25,5,76.7,66,2,0 +307,31,1,8.066,8,8.066,8.066,1,1,.,.,.,2,0,1,278.95597475387945,5.631,0,0,5,1,1,100,100,2,0,0,1,25,5,76.7,66,2,0 +308,31,1,12.755,12,12.755,12.755,1,1,.,.,.,2,0,1,129.04158259324583,4.86,0,0,5,1,1,100,100,2,0,0,1,25,5,76.7,66,2,0 +309,31,1,21.603,24,21.603,21.603,1,1,.,.,.,2,0,1,72.26218254554098,4.28,0,0,5,1,1,100,100,2,0,0,1,25,5,76.7,66,2,0 +310,31,1,46.183,48,46.183,46.183,2,2,.,.,.,2,0,1,19.7177609385627,2.982,0,0,5,1,1,100,100,2,0,0,1,25,5,76.7,66,2,0 +311,32,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,1,3,59,5,68.7,87,2,1 +312,32,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,1,3,59,5,68.7,87,2,0 +313,32,1,0.239,0.25,0.239,0.239,1,1,.,.,.,2,0,1,209.38004714554103,5.344,0,0,5,1,1,100,100,2,0,1,3,59,5,68.7,87,2,0 +314,32,1,0.472,0.5,0.472,0.472,1,1,.,.,.,2,0,1,314.4394170518481,5.751,0,0,5,1,1,100,100,2,0,1,3,59,5,68.7,87,2,0 +315,32,1,1.021,1,1.021,1.021,1,1,.,.,.,2,0,1,534.0461042200109,6.28,0,0,5,1,1,100,100,2,0,1,3,59,5,68.7,87,2,0 +316,32,1,4.054,4,4.054,4.054,1,1,.,.,.,2,0,1,410.68238133210656,6.018,0,0,5,1,1,100,100,2,0,1,3,59,5,68.7,87,2,0 +317,32,1,8.364,8,8.364,8.364,1,1,.,.,.,2,0,1,356.4663033318391,5.876,0,0,5,1,1,100,100,2,0,1,3,59,5,68.7,87,2,0 +318,32,1,12.166,12,12.166,12.166,1,1,.,.,.,2,0,1,217.25311863708745,5.381,0,0,5,1,1,100,100,2,0,1,3,59,5,68.7,87,2,0 +319,32,1,22.393,24,22.393,22.393,1,1,.,.,.,2,0,1,90.91086830675995,4.51,0,0,5,1,1,100,100,2,0,1,3,59,5,68.7,87,2,0 +320,32,1,50.914,48,50.914,50.914,3,2,.,.,.,2,0,1,19.25921190118953,2.958,0,0,5,1,1,100,100,2,0,1,3,59,5,68.7,87,2,0 +321,33,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,0,1,55,5,118.4,93,1,1 +322,33,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,0,1,55,5,118.4,93,1,0 +323,33,1,0.243,0.25,0.243,0.243,1,1,.,.,.,2,0,1,213.21220595975265,5.362,0,0,5,1,1,100,100,2,0,0,1,55,5,118.4,93,1,0 +324,33,1,0.526,0.5,0.526,0.526,1,1,.,.,.,2,0,1,237.9325450852972,5.472,0,0,5,1,1,100,100,2,0,0,1,55,5,118.4,93,1,0 +325,33,1,0.945,1,0.945,0.945,1,1,.,.,.,2,0,1,412.148485301454,6.021,0,0,5,1,1,100,100,2,0,0,1,55,5,118.4,93,1,0 +326,33,1,3.744,4,3.744,3.744,1,1,.,.,.,2,0,1,258.31831462147653,5.554,0,0,5,1,1,100,100,2,0,0,1,55,5,118.4,93,1,0 +327,33,1,8.65,8,8.65,8.65,1,1,.,.,.,2,0,1,74.05403803351632,4.305,0,0,5,1,1,100,100,2,0,0,1,55,5,118.4,93,1,0 +328,33,1,12.504,12,12.504,12.504,1,1,.,.,.,2,0,1,69.30841715678122,4.239,0,0,5,1,1,100,100,2,0,0,1,55,5,118.4,93,1,0 +329,33,1,22.47,24,22.47,22.47,1,1,.,.,.,2,0,1,37.69253636479176,3.629,0,0,5,1,1,100,100,2,0,0,1,55,5,118.4,93,1,0 +330,33,1,51.637,48,51.637,51.637,3,2,.,.,.,2,0,1,.,.,1,1,5,1,1,100,100,2,0,0,1,55,5,118.4,93,1,0 +331,34,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,0,1,18,5,78.7,87,2,1 +332,34,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,0,1,18,5,78.7,87,2,0 +333,34,1,0.248,0.25,0.248,0.248,1,1,.,.,.,2,0,1,175.1841190719185,5.166,0,0,5,1,1,100,100,2,0,0,1,18,5,78.7,87,2,0 +334,34,1,0.484,0.5,0.484,0.484,1,1,.,.,.,2,0,1,255.66078932669444,5.544,0,0,5,1,1,100,100,2,0,0,1,18,5,78.7,87,2,0 +335,34,1,1.054,1,1.054,1.054,1,1,.,.,.,2,0,1,356.95756025824636,5.878,0,0,5,1,1,100,100,2,0,0,1,18,5,78.7,87,2,0 +336,34,1,4.35,4,4.35,4.35,1,1,.,.,.,2,0,1,421.6082101091455,6.044,0,0,5,1,1,100,100,2,0,0,1,18,5,78.7,87,2,0 +337,34,1,7.944,8,7.944,7.944,1,1,.,.,.,2,0,1,233.46589044472384,5.453,0,0,5,1,1,100,100,2,0,0,1,18,5,78.7,87,2,0 +338,34,1,12.021,12,12.021,12.021,1,1,.,.,.,2,0,1,159.56635697237425,5.072,0,0,5,1,1,100,100,2,0,0,1,18,5,78.7,87,2,0 +339,34,1,24.531,24,24.531,24.531,2,1,.,.,.,2,0,1,112.21137480018247,4.72,0,0,5,1,1,100,100,2,0,0,1,18,5,78.7,87,2,0 +340,34,1,50.619,48,50.619,50.619,3,2,.,.,.,2,0,1,15.135335879472295,2.717,0,0,5,1,1,100,100,2,0,0,1,18,5,78.7,87,2,0 +341,35,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,0,1,26,5,70.6,115,1,1 +342,35,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,0,1,26,5,70.6,115,1,0 +343,35,1,0.271,0.25,0.271,0.271,1,1,.,.,.,2,0,1,259.75032096874577,5.56,0,0,5,1,1,100,100,2,0,0,1,26,5,70.6,115,1,0 +344,35,1,0.541,0.5,0.541,0.541,1,1,.,.,.,2,0,1,355.6565453010379,5.874,0,0,5,1,1,100,100,2,0,0,1,26,5,70.6,115,1,0 +345,35,1,0.966,1,0.966,0.966,1,1,.,.,.,2,0,1,530.4126812723224,6.274,0,0,5,1,1,100,100,2,0,0,1,26,5,70.6,115,1,0 +346,35,1,4.382,4,4.382,4.382,1,1,.,.,.,2,0,1,247.91087855272198,5.513,0,0,5,1,1,100,100,2,0,0,1,26,5,70.6,115,1,0 +347,35,1,7.695,8,7.695,7.695,1,1,.,.,.,2,0,1,143.06893918328112,4.963,0,0,5,1,1,100,100,2,0,0,1,26,5,70.6,115,1,0 +348,35,1,11.916,12,11.916,11.916,1,1,.,.,.,2,0,1,69.63483550917275,4.243,0,0,5,1,1,100,100,2,0,0,1,26,5,70.6,115,1,0 +349,35,1,24.764,24,24.764,24.764,2,1,.,.,.,2,0,1,26.390315544961517,3.273,0,0,5,1,1,100,100,2,0,0,1,26,5,70.6,115,1,0 +350,35,1,43.365,48,43.365,43.365,2,2,.,.,.,2,0,1,6.121427045679128,1.812,0,0,5,1,1,100,100,2,0,0,1,26,5,70.6,115,1,0 +351,36,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,0,1,32,5,101.8,108,1,1 +352,36,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,0,1,32,5,101.8,108,1,0 +353,36,1,0.241,0.25,0.241,0.241,1,1,.,.,.,2,0,1,92.14330189090325,4.523,0,0,5,1,1,100,100,2,0,0,1,32,5,101.8,108,1,0 +354,36,1,0.489,0.5,0.489,0.489,1,1,.,.,.,2,0,1,130.06109300627864,4.868,0,0,5,1,1,100,100,2,0,0,1,32,5,101.8,108,1,0 +355,36,1,1.001,1,1.001,1.001,1,1,.,.,.,2,0,1,240.6604885297929,5.483,0,0,5,1,1,100,100,2,0,0,1,32,5,101.8,108,1,0 +356,36,1,3.922,4,3.922,3.922,1,1,.,.,.,2,0,1,194.5488680965122,5.271,0,0,5,1,1,100,100,2,0,0,1,32,5,101.8,108,1,0 +357,36,1,7.923,8,7.923,7.923,1,1,.,.,.,2,0,1,97.52918332984855,4.58,0,0,5,1,1,100,100,2,0,0,1,32,5,101.8,108,1,0 +358,36,1,11.307,12,11.307,11.307,1,1,.,.,.,2,0,1,79.59906491656149,4.377,0,0,5,1,1,100,100,2,0,0,1,32,5,101.8,108,1,0 +359,36,1,24.141,24,24.141,24.141,2,1,.,.,.,2,0,1,31.1037045050106,3.437,0,0,5,1,1,100,100,2,0,0,1,32,5,101.8,108,1,0 +360,36,1,45.224,48,45.224,45.224,2,2,.,.,.,2,0,1,7.104904209836037,1.961,0,0,5,1,1,100,100,2,0,0,1,32,5,101.8,108,1,0 +361,37,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,1,3,20,5,49.8,120,1,1 +362,37,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,1,3,20,5,49.8,120,1,0 +363,37,1,0.226,0.25,0.226,0.226,1,1,.,.,.,2,0,1,714.5875303451953,6.572,0,0,5,1,1,100,100,2,0,1,3,20,5,49.8,120,1,0 +364,37,1,0.457,0.5,0.457,0.457,1,1,.,.,.,2,0,1,505.27779167163965,6.225,0,0,5,1,1,100,100,2,0,1,3,20,5,49.8,120,1,0 +365,37,1,0.936,1,0.936,0.936,1,1,.,.,.,2,0,1,486.84902675893267,6.188,0,0,5,1,1,100,100,2,0,1,3,20,5,49.8,120,1,0 +366,37,1,3.843,4,3.843,3.843,1,1,.,.,.,2,0,1,499.8584610167993,6.214,0,0,5,1,1,100,100,2,0,1,3,20,5,49.8,120,1,0 +367,37,1,8.596,8,8.596,8.596,1,1,.,.,.,2,0,1,294.88663312329834,5.687,0,0,5,1,1,100,100,2,0,1,3,20,5,49.8,120,1,0 +368,37,1,12.729,12,12.729,12.729,1,1,.,.,.,2,0,1,171.45748076351995,5.144,0,0,5,1,1,100,100,2,0,1,3,20,5,49.8,120,1,0 +369,37,1,23.454,24,23.454,23.454,1,1,.,.,.,2,0,1,74.51515345731454,4.311,0,0,5,1,1,100,100,2,0,1,3,20,5,49.8,120,1,0 +370,37,1,47.366,48,47.366,47.366,2,2,.,.,.,2,0,1,11.36446760669203,2.43,0,0,5,1,1,100,100,2,0,1,3,20,5,49.8,120,1,0 +371,38,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,1,3,33,5,81.5,114,1,1 +372,38,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,1,3,33,5,81.5,114,1,0 +373,38,1,0.242,0.25,0.242,0.242,1,1,.,.,.,2,0,1,146.6037367551065,4.988,0,0,5,1,1,100,100,2,0,1,3,33,5,81.5,114,1,0 +374,38,1,0.465,0.5,0.465,0.465,1,1,.,.,.,2,0,1,141.58748922011867,4.953,0,0,5,1,1,100,100,2,0,1,3,33,5,81.5,114,1,0 +375,38,1,1.009,1,1.009,1.009,1,1,.,.,.,2,0,1,347.37060742071884,5.85,0,0,5,1,1,100,100,2,0,1,3,33,5,81.5,114,1,0 +376,38,1,4.036,4,4.036,4.036,1,1,.,.,.,2,0,1,314.0473880836127,5.75,0,0,5,1,1,100,100,2,0,1,3,33,5,81.5,114,1,0 +377,38,1,7.383,8,7.383,7.383,1,1,.,.,.,2,0,1,289.44340519216433,5.668,0,0,5,1,1,100,100,2,0,1,3,33,5,81.5,114,1,0 +378,38,1,11.599,12,11.599,11.599,1,1,.,.,.,2,0,1,202.39931424507364,5.31,0,0,5,1,1,100,100,2,0,1,3,33,5,81.5,114,1,0 +379,38,1,22.423,24,22.423,22.423,1,1,.,.,.,2,0,1,98.29273078592666,4.588,0,0,5,1,1,100,100,2,0,1,3,33,5,81.5,114,1,0 +380,38,1,50.282,48,50.282,50.282,3,2,.,.,.,2,0,1,25.87417426625283,3.253,0,0,5,1,1,100,100,2,0,1,3,33,5,81.5,114,1,0 +381,39,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,1,3,32,5,94.8,81,2,1 +382,39,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,1,3,32,5,94.8,81,2,0 +383,39,1,0.272,0.25,0.272,0.272,1,1,.,.,.,2,0,1,119.42862979309571,4.783,0,0,5,1,1,100,100,2,0,1,3,32,5,94.8,81,2,0 +384,39,1,0.51,0.5,0.51,0.51,1,1,.,.,.,2,0,1,119.39773889458473,4.782,0,0,5,1,1,100,100,2,0,1,3,32,5,94.8,81,2,0 +385,39,1,0.929,1,0.929,0.929,1,1,.,.,.,2,0,1,213.7534135643262,5.365,0,0,5,1,1,100,100,2,0,1,3,32,5,94.8,81,2,0 +386,39,1,4.313,4,4.313,4.313,1,1,.,.,.,2,0,1,224.59238200390578,5.414,0,0,5,1,1,100,100,2,0,1,3,32,5,94.8,81,2,0 +387,39,1,7.393,8,7.393,7.393,1,1,.,.,.,2,0,1,225.611014091282,5.419,0,0,5,1,1,100,100,2,0,1,3,32,5,94.8,81,2,0 +388,39,1,12.781,12,12.781,12.781,1,1,.,.,.,2,0,1,148.69205067377013,5.002,0,0,5,1,1,100,100,2,0,1,3,32,5,94.8,81,2,0 +389,39,1,23.54,24,23.54,23.54,1,1,.,.,.,2,0,1,86.76132562525922,4.463,0,0,5,1,1,100,100,2,0,1,3,32,5,94.8,81,2,0 +390,39,1,48.716,48,48.716,48.716,3,2,.,.,.,2,0,1,17.93576305519209,2.887,0,0,5,1,1,100,100,2,0,1,3,32,5,94.8,81,2,0 +391,40,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,0,1,36,5,88,90,1,1 +392,40,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,0,1,36,5,88,90,1,0 +393,40,1,0.238,0.25,0.238,0.238,1,1,.,.,.,2,0,1,74.18710364140956,4.307,0,0,5,1,1,100,100,2,0,0,1,36,5,88,90,1,0 +394,40,1,0.509,0.5,0.509,0.509,1,1,.,.,.,2,0,1,149.59183530698542,5.008,0,0,5,1,1,100,100,2,0,0,1,36,5,88,90,1,0 +395,40,1,0.987,1,0.987,0.987,1,1,.,.,.,2,0,1,258.033567072461,5.553,0,0,5,1,1,100,100,2,0,0,1,36,5,88,90,1,0 +396,40,1,4.321,4,4.321,4.321,1,1,.,.,.,2,0,1,302.7348011590723,5.713,0,0,5,1,1,100,100,2,0,0,1,36,5,88,90,1,0 +397,40,1,8.479,8,8.479,8.479,1,1,.,.,.,2,0,1,210.77800562478274,5.351,0,0,5,1,1,100,100,2,0,0,1,36,5,88,90,1,0 +398,40,1,11.36,12,11.36,11.36,1,1,.,.,.,2,0,1,125.81110899573154,4.835,0,0,5,1,1,100,100,2,0,0,1,36,5,88,90,1,0 +399,40,1,26.127,24,26.127,26.127,2,1,.,.,.,2,0,1,64.22148187958773,4.162,0,0,5,1,1,100,100,2,0,0,1,36,5,88,90,1,0 +400,40,1,48.143,48,48.143,48.143,3,2,.,.,.,2,0,1,18.97678035742815,2.943,0,0,5,1,1,100,100,2,0,0,1,36,5,88,90,1,0 +401,41,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,1,3,30,5,64.6,67,2,1 +402,41,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,1,3,30,5,64.6,67,2,0 +403,41,1,0.244,0.25,0.244,0.244,1,1,.,.,.,2,0,1,1210.196088378619,7.099,0,0,5,1,1,500,500,3,0,1,3,30,5,64.6,67,2,0 +404,41,1,0.518,0.5,0.518,0.518,1,1,.,.,.,2,0,1,1489.9712370941954,7.307,0,0,5,1,1,500,500,3,0,1,3,30,5,64.6,67,2,0 +405,41,1,1.044,1,1.044,1.044,1,1,.,.,.,2,0,1,1662.9062446182757,7.416,0,0,5,1,1,500,500,3,0,1,3,30,5,64.6,67,2,0 +406,41,1,3.709,4,3.709,3.709,1,1,.,.,.,2,0,1,3050.030677854217,8.023,0,0,5,1,1,500,500,3,0,1,3,30,5,64.6,67,2,0 +407,41,1,8.589,8,8.589,8.589,1,1,.,.,.,2,0,1,1634.2413748597758,7.399,0,0,5,1,1,500,500,3,0,1,3,30,5,64.6,67,2,0 +408,41,1,12.619,12,12.619,12.619,1,1,.,.,.,2,0,1,1218.8235200879453,7.106,0,0,5,1,1,500,500,3,0,1,3,30,5,64.6,67,2,0 +409,41,1,23.299,24,23.299,23.299,1,1,.,.,.,2,0,1,646.6535479153325,6.472,0,0,5,1,1,500,500,3,0,1,3,30,5,64.6,67,2,0 +410,41,1,45.557,48,45.557,45.557,2,2,.,.,.,2,0,1,183.31471481657607,5.211,0,0,5,1,1,500,500,3,0,1,3,30,5,64.6,67,2,0 +411,42,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,1,1,38,5,106.9,104,1,1 +412,42,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,1,1,38,5,106.9,104,1,0 +413,42,1,0.238,0.25,0.238,0.238,1,1,.,.,.,2,0,1,694.1577407394153,6.543,0,0,5,1,1,500,500,3,0,1,1,38,5,106.9,104,1,0 +414,42,1,0.498,0.5,0.498,0.498,1,1,.,.,.,2,0,1,1177.223612592839,7.071,0,0,5,1,1,500,500,3,0,1,1,38,5,106.9,104,1,0 +415,42,1,0.941,1,0.941,0.941,1,1,.,.,.,2,0,1,1675.185070328822,7.424,0,0,5,1,1,500,500,3,0,1,1,38,5,106.9,104,1,0 +416,42,1,4.064,4,4.064,4.064,1,1,.,.,.,2,0,1,1240.0920678851246,7.123,0,0,5,1,1,500,500,3,0,1,1,38,5,106.9,104,1,0 +417,42,1,8.707,8,8.707,8.707,1,1,.,.,.,2,0,1,389.8225287977387,5.966,0,0,5,1,1,500,500,3,0,1,1,38,5,106.9,104,1,0 +418,42,1,11.334,12,11.334,11.334,1,1,.,.,.,2,0,1,691.1182650238636,6.538,0,0,5,1,1,500,500,3,0,1,1,38,5,106.9,104,1,0 +419,42,1,25.728,24,25.728,25.728,2,1,.,.,.,2,0,1,165.58136357618537,5.109,0,0,5,1,1,500,500,3,0,1,1,38,5,106.9,104,1,0 +420,42,1,45.205,48,45.205,45.205,2,2,.,.,.,2,0,1,39.55613108072633,3.678,0,0,5,1,1,500,500,3,0,1,1,38,5,106.9,104,1,0 +421,43,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,0,1,60,5,51,95,1,1 +422,43,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,0,1,60,5,51,95,1,0 +423,43,1,0.257,0.25,0.257,0.257,1,1,.,.,.,2,0,1,1258.8544769411933,7.138,0,0,5,1,1,500,500,3,0,0,1,60,5,51,95,1,0 +424,43,1,0.504,0.5,0.504,0.504,1,1,.,.,.,2,0,1,2845.8104635202058,7.954,0,0,5,1,1,500,500,3,0,0,1,60,5,51,95,1,0 +425,43,1,1.098,1,1.098,1.098,1,1,.,.,.,2,0,1,1501.2865133670275,7.314,0,0,5,1,1,500,500,3,0,0,1,60,5,51,95,1,0 +426,43,1,4.073,4,4.073,4.073,1,1,.,.,.,2,0,1,2895.0981410221657,7.971,0,0,5,1,1,500,500,3,0,0,1,60,5,51,95,1,0 +427,43,1,8.381,8,8.381,8.381,1,1,.,.,.,2,0,1,1900.2506123458502,7.55,0,0,5,1,1,500,500,3,0,0,1,60,5,51,95,1,0 +428,43,1,11.461,12,11.461,11.461,1,1,.,.,.,2,0,1,1244.4713734811296,7.126,0,0,5,1,1,500,500,3,0,0,1,60,5,51,95,1,0 +429,43,1,25.87,24,25.87,25.87,2,1,.,.,.,2,0,1,452.49576201265836,6.115,0,0,5,1,1,500,500,3,0,0,1,60,5,51,95,1,0 +430,43,1,47.95,48,47.95,47.95,2,2,.,.,.,2,0,1,96.61471535118692,4.571,0,0,5,1,1,500,500,3,0,0,1,60,5,51,95,1,0 +431,44,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,1,3,40,5,85.2,116,1,1 +432,44,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,1,3,40,5,85.2,116,1,0 +433,44,1,0.267,0.25,0.267,0.267,1,1,.,.,.,2,0,1,694.322069543139,6.543,0,0,5,1,1,500,500,3,0,1,3,40,5,85.2,116,1,0 +434,44,1,0.514,0.5,0.514,0.514,1,1,.,.,.,2,0,1,1362.2730299251068,7.217,0,0,5,1,1,500,500,3,0,1,3,40,5,85.2,116,1,0 +435,44,1,1.007,1,1.007,1.007,1,1,.,.,.,2,0,1,1773.3641652706197,7.481,0,0,5,1,1,500,500,3,0,1,3,40,5,85.2,116,1,0 +436,44,1,4.22,4,4.22,4.22,1,1,.,.,.,2,0,1,1424.1795681153835,7.261,0,0,5,1,1,500,500,3,0,1,3,40,5,85.2,116,1,0 +437,44,1,8.58,8,8.58,8.58,1,1,.,.,.,2,0,1,545.3931726868615,6.302,0,0,5,1,1,500,500,3,0,1,3,40,5,85.2,116,1,0 +438,44,1,13.032,12,13.032,13.032,1,1,.,.,.,2,0,1,434.8986097896178,6.075,0,0,5,1,1,500,500,3,0,1,3,40,5,85.2,116,1,0 +439,44,1,23.386,24,23.386,23.386,1,1,.,.,.,2,0,1,253.72014105895803,5.536,0,0,5,1,1,500,500,3,0,1,3,40,5,85.2,116,1,0 +440,44,1,46.653,48,46.653,46.653,2,2,.,.,.,2,0,1,54.35172527397407,3.995,0,0,5,1,1,500,500,3,0,1,3,40,5,85.2,116,1,0 +441,45,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,1,3,56,5,86.4,73,2,1 +442,45,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,1,3,56,5,86.4,73,2,0 +443,45,1,0.246,0.25,0.246,0.246,1,1,.,.,.,2,0,1,804.8702810726105,6.691,0,0,5,1,1,500,500,3,0,1,3,56,5,86.4,73,2,0 +444,45,1,0.536,0.5,0.536,0.536,1,1,.,.,.,2,0,1,1181.3332224649944,7.074,0,0,5,1,1,500,500,3,0,1,3,56,5,86.4,73,2,0 +445,45,1,1.063,1,1.063,1.063,1,1,.,.,.,2,0,1,2497.028045612403,7.823,0,0,5,1,1,500,500,3,0,1,3,56,5,86.4,73,2,0 +446,45,1,3.964,4,3.964,3.964,1,1,.,.,.,2,0,1,2512.2240552920193,7.829,0,0,5,1,1,500,500,3,0,1,3,56,5,86.4,73,2,0 +447,45,1,7.543,8,7.543,7.543,1,1,.,.,.,2,0,1,1333.5868782689322,7.196,0,0,5,1,1,500,500,3,0,1,3,56,5,86.4,73,2,0 +448,45,1,11.411,12,11.411,11.411,1,1,.,.,.,2,0,1,820.4219622397109,6.71,0,0,5,1,1,500,500,3,0,1,3,56,5,86.4,73,2,0 +449,45,1,23.83,24,23.83,23.83,1,1,.,.,.,2,0,1,375.20564495957007,5.927,0,0,5,1,1,500,500,3,0,1,3,56,5,86.4,73,2,0 +450,45,1,46.655,48,46.655,46.655,2,2,.,.,.,2,0,1,122.25741617796973,4.806,0,0,5,1,1,500,500,3,0,1,3,56,5,86.4,73,2,0 +451,46,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,1,3,20,5,55,94,1,1 +452,46,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,1,3,20,5,55,94,1,0 +453,46,1,0.275,0.25,0.275,0.275,1,1,.,.,.,2,0,1,530.1141147953124,6.273,0,0,5,1,1,500,500,3,0,1,3,20,5,55,94,1,0 +454,46,1,0.461,0.5,0.461,0.461,1,1,.,.,.,2,0,1,1574.7873845976521,7.362,0,0,5,1,1,500,500,3,0,1,3,20,5,55,94,1,0 +455,46,1,0.901,1,0.901,0.901,1,1,.,.,.,2,0,1,1996.340517165985,7.599,0,0,5,1,1,500,500,3,0,1,3,20,5,55,94,1,0 +456,46,1,3.938,4,3.938,3.938,1,1,.,.,.,2,0,1,2906.739010784798,7.975,0,0,5,1,1,500,500,3,0,1,3,20,5,55,94,1,0 +457,46,1,8.367,8,8.367,8.367,1,1,.,.,.,2,0,1,2081.86044079574,7.641,0,0,5,1,1,500,500,3,0,1,3,20,5,55,94,1,0 +458,46,1,12.989,12,12.989,12.989,1,1,.,.,.,2,0,1,1362.6834120405356,7.217,0,0,5,1,1,500,500,3,0,1,3,20,5,55,94,1,0 +459,46,1,23.458,24,23.458,23.458,1,1,.,.,.,2,0,1,612.8696360409117,6.418,0,0,5,1,1,500,500,3,0,1,3,20,5,55,94,1,0 +460,46,1,43.546,48,43.546,43.546,2,2,.,.,.,2,0,1,293.32085071643394,5.681,0,0,5,1,1,500,500,3,0,1,3,20,5,55,94,1,0 +461,47,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,0,1,57,5,46.3,81,2,1 +462,47,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,0,1,57,5,46.3,81,2,0 +463,47,1,0.242,0.25,0.242,0.242,1,1,.,.,.,2,0,1,850.9881094935341,6.746,0,0,5,1,1,500,500,3,0,0,1,57,5,46.3,81,2,0 +464,47,1,0.505,0.5,0.505,0.505,1,1,.,.,.,2,0,1,1408.8070626275771,7.25,0,0,5,1,1,500,500,3,0,0,1,57,5,46.3,81,2,0 +465,47,1,1.026,1,1.026,1.026,1,1,.,.,.,2,0,1,2239.345814972832,7.714,0,0,5,1,1,500,500,3,0,0,1,57,5,46.3,81,2,0 +466,47,1,4.27,4,4.27,4.27,1,1,.,.,.,2,0,1,2251.9592373789524,7.72,0,0,5,1,1,500,500,3,0,0,1,57,5,46.3,81,2,0 +467,47,1,8.634,8,8.634,8.634,1,1,.,.,.,2,0,1,992.8547164511903,6.901,0,0,5,1,1,500,500,3,0,0,1,57,5,46.3,81,2,0 +468,47,1,11.214,12,11.214,11.214,1,1,.,.,.,2,0,1,1286.4675602822183,7.16,0,0,5,1,1,500,500,3,0,0,1,57,5,46.3,81,2,0 +469,47,1,22.826,24,22.826,22.826,1,1,.,.,.,2,0,1,324.8180696915133,5.783,0,0,5,1,1,500,500,3,0,0,1,57,5,46.3,81,2,0 +470,47,1,45.562,48,45.562,45.562,2,2,.,.,.,2,0,1,62.450503700111874,4.134,0,0,5,1,1,500,500,3,0,0,1,57,5,46.3,81,2,0 +471,48,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,1,1,56,5,51.5,107,1,1 +472,48,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,1,1,56,5,51.5,107,1,0 +473,48,1,0.226,0.25,0.226,0.226,1,1,.,.,.,2,0,1,594.1945716822511,6.387,0,0,5,1,1,500,500,3,0,1,1,56,5,51.5,107,1,0 +474,48,1,0.517,0.5,0.517,0.517,1,1,.,.,.,2,0,1,929.0369170922156,6.834,0,0,5,1,1,500,500,3,0,1,1,56,5,51.5,107,1,0 +475,48,1,0.973,1,0.973,0.973,1,1,.,.,.,2,0,1,2228.266905433397,7.709,0,0,5,1,1,500,500,3,0,1,1,56,5,51.5,107,1,0 +476,48,1,3.753,4,3.753,3.753,1,1,.,.,.,2,0,1,2206.4914728954755,7.699,0,0,5,1,1,500,500,3,0,1,1,56,5,51.5,107,1,0 +477,48,1,8.567,8,8.567,8.567,1,1,.,.,.,2,0,1,1190.6485468609856,7.082,0,0,5,1,1,500,500,3,0,1,1,56,5,51.5,107,1,0 +478,48,1,11.395,12,11.395,11.395,1,1,.,.,.,2,0,1,844.8786553764309,6.739,0,0,5,1,1,500,500,3,0,1,1,56,5,51.5,107,1,0 +479,48,1,21.948,24,21.948,21.948,1,1,.,.,.,2,0,1,141.0140402566954,4.949,0,0,5,1,1,500,500,3,0,1,1,56,5,51.5,107,1,0 +480,48,1,51.171,48,51.171,51.171,3,2,.,.,.,2,0,1,24.952635015648212,3.217,0,0,5,1,1,500,500,3,0,1,1,56,5,51.5,107,1,0 +481,49,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,1,1,60,5,63.2,96,1,1 +482,49,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,1,1,60,5,63.2,96,1,0 +483,49,1,0.236,0.25,0.236,0.236,1,1,.,.,.,2,0,1,1152.4782799621603,7.05,0,0,5,1,1,500,500,3,0,1,1,60,5,63.2,96,1,0 +484,49,1,0.531,0.5,0.531,0.531,1,1,.,.,.,2,0,1,1172.9168753436213,7.067,0,0,5,1,1,500,500,3,0,1,1,60,5,63.2,96,1,0 +485,49,1,1.071,1,1.071,1.071,1,1,.,.,.,2,0,1,2276.057567785654,7.73,0,0,5,1,1,500,500,3,0,1,1,60,5,63.2,96,1,0 +486,49,1,4.349,4,4.349,4.349,1,1,.,.,.,2,0,1,1518.3472723009234,7.325,0,0,5,1,1,500,500,3,0,1,1,60,5,63.2,96,1,0 +487,49,1,8.487,8,8.487,8.487,1,1,.,.,.,2,0,1,552.0924722781742,6.314,0,0,5,1,1,500,500,3,0,1,1,60,5,63.2,96,1,0 +488,49,1,10.909,12,10.909,10.909,1,1,.,.,.,2,0,1,204.69141835344024,5.322,0,0,5,1,1,500,500,3,0,1,1,60,5,63.2,96,1,0 +489,49,1,22.909,24,22.909,22.909,1,1,.,.,.,2,0,1,73.96258761618584,4.304,0,0,5,1,1,500,500,3,0,1,1,60,5,63.2,96,1,0 +490,49,1,48.601,48,48.601,48.601,3,2,.,.,.,2,0,1,9.298505884937125,2.23,0,0,5,1,1,500,500,3,0,1,1,60,5,63.2,96,1,0 +491,50,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,0,1,55,5,62,88,2,1 +492,50,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,0,1,55,5,62,88,2,0 +493,50,1,0.26,0.25,0.26,0.26,1,1,.,.,.,2,0,1,1268.7642782053022,7.146,0,0,5,1,1,500,500,3,0,0,1,55,5,62,88,2,0 +494,50,1,0.525,0.5,0.525,0.525,1,1,.,.,.,2,0,1,1870.8107483071844,7.534,0,0,5,1,1,500,500,3,0,0,1,55,5,62,88,2,0 +495,50,1,0.917,1,0.917,0.917,1,1,.,.,.,2,0,1,2806.429763197388,7.94,0,0,5,1,1,500,500,3,0,0,1,55,5,62,88,2,0 +496,50,1,4.158,4,4.158,4.158,1,1,.,.,.,2,0,1,2055.258599045424,7.628,0,0,5,1,1,500,500,3,0,0,1,55,5,62,88,2,0 +497,50,1,7.812,8,7.812,7.812,1,1,.,.,.,2,0,1,1161.0777684680008,7.057,0,0,5,1,1,500,500,3,0,0,1,55,5,62,88,2,0 +498,50,1,12.98,12,12.98,12.98,1,1,.,.,.,2,0,1,413.9559710466597,6.026,0,0,5,1,1,500,500,3,0,0,1,55,5,62,88,2,0 +499,50,1,22.278,24,22.278,22.278,1,1,.,.,.,2,0,1,281.1656255092639,5.639,0,0,5,1,1,500,500,3,0,0,1,55,5,62,88,2,0 +500,50,1,46.059,48,46.059,46.059,2,2,.,.,.,2,0,1,28.368968367091828,3.345,0,0,5,1,1,500,500,3,0,0,1,55,5,62,88,2,0 +501,51,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,1,3,60,5,54.5,86,2,1 +502,51,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,1,3,60,5,54.5,86,2,0 +503,51,1,0.262,0.25,0.262,0.262,1,1,.,.,.,2,0,1,377.34965495114795,5.933,0,0,5,1,1,500,500,3,0,1,3,60,5,54.5,86,2,0 +504,51,1,0.474,0.5,0.474,0.474,1,1,.,.,.,2,0,1,1723.6807022722144,7.452,0,0,5,1,1,500,500,3,0,1,3,60,5,54.5,86,2,0 +505,51,1,1.093,1,1.093,1.093,1,1,.,.,.,2,0,1,2985.04693083381,8.001,0,0,5,1,1,500,500,3,0,1,3,60,5,54.5,86,2,0 +506,51,1,4.382,4,4.382,4.382,1,1,.,.,.,2,0,1,2454.769906316115,7.806,0,0,5,1,1,500,500,3,0,1,3,60,5,54.5,86,2,0 +507,51,1,7.289,8,7.289,7.289,1,1,.,.,.,2,0,1,2225.937879401348,7.708,0,0,5,1,1,500,500,3,0,1,3,60,5,54.5,86,2,0 +508,51,1,11.422,12,11.422,11.422,1,1,.,.,.,2,0,1,1886.2020335135392,7.542,0,0,5,1,1,500,500,3,0,1,3,60,5,54.5,86,2,0 +509,51,1,26.15,24,26.15,26.15,2,1,.,.,.,2,0,1,724.8118891816262,6.586,0,0,5,1,1,500,500,3,0,1,3,60,5,54.5,86,2,0 +510,51,1,44.434,48,44.434,44.434,2,2,.,.,.,2,0,1,258.37952426646353,5.554,0,0,5,1,1,500,500,3,0,1,3,60,5,54.5,86,2,0 +511,52,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,0,1,55,5,86.7,110,1,1 +512,52,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,0,1,55,5,86.7,110,1,0 +513,52,1,0.25,0.25,0.25,0.25,1,1,.,.,.,2,0,1,850.8797750630605,6.746,0,0,5,1,1,500,500,3,0,0,1,55,5,86.7,110,1,0 +514,52,1,0.513,0.5,0.513,0.513,1,1,.,.,.,2,0,1,832.4959084492733,6.724,0,0,5,1,1,500,500,3,0,0,1,55,5,86.7,110,1,0 +515,52,1,0.905,1,0.905,0.905,1,1,.,.,.,2,0,1,1929.7487504575333,7.565,0,0,5,1,1,500,500,3,0,0,1,55,5,86.7,110,1,0 +516,52,1,3.713,4,3.713,3.713,1,1,.,.,.,2,0,1,1503.629837610226,7.316,0,0,5,1,1,500,500,3,0,0,1,55,5,86.7,110,1,0 +517,52,1,8.394,8,8.394,8.394,1,1,.,.,.,2,0,1,377.5273082658617,5.934,0,0,5,1,1,500,500,3,0,0,1,55,5,86.7,110,1,0 +518,52,1,11.624,12,11.624,11.624,1,1,.,.,.,2,0,1,366.1264126950567,5.903,0,0,5,1,1,500,500,3,0,0,1,55,5,86.7,110,1,0 +519,52,1,25.312,24,25.312,25.312,2,1,.,.,.,2,0,1,128.01771084857359,4.852,0,0,5,1,1,500,500,3,0,0,1,55,5,86.7,110,1,0 +520,52,1,47.499,48,47.499,47.499,2,2,.,.,.,2,0,1,24.934118115982074,3.216,0,0,5,1,1,500,500,3,0,0,1,55,5,86.7,110,1,0 +521,53,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,0,1,35,5,68.3,78,2,1 +522,53,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,0,1,35,5,68.3,78,2,0 +523,53,1,0.265,0.25,0.265,0.265,1,1,.,.,.,2,0,1,1060.8446797291638,6.967,0,0,5,1,1,500,500,3,0,0,1,35,5,68.3,78,2,0 +524,53,1,0.515,0.5,0.515,0.515,1,1,.,.,.,2,0,1,1155.9104854896611,7.053,0,0,5,1,1,500,500,3,0,0,1,35,5,68.3,78,2,0 +525,53,1,0.994,1,0.994,0.994,1,1,.,.,.,2,0,1,2467.4818936851025,7.811,0,0,5,1,1,500,500,3,0,0,1,35,5,68.3,78,2,0 +526,53,1,4.322,4,4.322,4.322,1,1,.,.,.,2,0,1,1895.5086966521187,7.547,0,0,5,1,1,500,500,3,0,0,1,35,5,68.3,78,2,0 +527,53,1,7.42,8,7.42,7.42,1,1,.,.,.,2,0,1,870.229857898761,6.769,0,0,5,1,1,500,500,3,0,0,1,35,5,68.3,78,2,0 +528,53,1,13.137,12,13.137,13.137,1,1,.,.,.,2,0,1,415.5624673134164,6.03,0,0,5,1,1,500,500,3,0,0,1,35,5,68.3,78,2,0 +529,53,1,23.334,24,23.334,23.334,1,1,.,.,.,2,0,1,166.28396823495498,5.114,0,0,5,1,1,500,500,3,0,0,1,35,5,68.3,78,2,0 +530,53,1,50.148,48,50.148,50.148,3,2,.,.,.,2,0,1,17.74027441920101,2.876,0,0,5,1,1,500,500,3,0,0,1,35,5,68.3,78,2,0 +531,54,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,0,1,58,5,54.4,88,2,1 +532,54,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,0,1,58,5,54.4,88,2,0 +533,54,1,0.228,0.25,0.228,0.228,1,1,.,.,.,2,0,1,1458.6233225069943,7.285,0,0,5,1,1,500,500,3,0,0,1,58,5,54.4,88,2,0 +534,54,1,0.535,0.5,0.535,0.535,1,1,.,.,.,2,0,1,1412.9101163175617,7.253,0,0,5,1,1,500,500,3,0,0,1,58,5,54.4,88,2,0 +535,54,1,1.069,1,1.069,1.069,1,1,.,.,.,2,0,1,2495.004713385167,7.822,0,0,5,1,1,500,500,3,0,0,1,58,5,54.4,88,2,0 +536,54,1,4.008,4,4.008,4.008,1,1,.,.,.,2,0,1,2418.646142051335,7.791,0,0,5,1,1,500,500,3,0,0,1,58,5,54.4,88,2,0 +537,54,1,7.513,8,7.513,7.513,1,1,.,.,.,2,0,1,1660.560678153732,7.415,0,0,5,1,1,500,500,3,0,0,1,58,5,54.4,88,2,0 +538,54,1,12.6,12,12.6,12.6,1,1,.,.,.,2,0,1,975.0038380739371,6.882,0,0,5,1,1,500,500,3,0,0,1,58,5,54.4,88,2,0 +539,54,1,22.528,24,22.528,22.528,1,1,.,.,.,2,0,1,371.0473205226491,5.916,0,0,5,1,1,500,500,3,0,0,1,58,5,54.4,88,2,0 +540,54,1,48.058,48,48.058,48.058,3,2,.,.,.,2,0,1,102.30140941716445,4.628,0,0,5,1,1,500,500,3,0,0,1,58,5,54.4,88,2,0 +541,55,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,0,1,40,5,54.3,115,1,1 +542,55,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,0,1,40,5,54.3,115,1,0 +543,55,1,0.274,0.25,0.274,0.274,1,1,.,.,.,2,0,1,1572.2894606730435,7.36,0,0,5,1,1,500,500,3,0,0,1,40,5,54.3,115,1,0 +544,55,1,0.529,0.5,0.529,0.529,1,1,.,.,.,2,0,1,2805.1353384757554,7.939,0,0,5,1,1,500,500,3,0,0,1,40,5,54.3,115,1,0 +545,55,1,1.066,1,1.066,1.066,1,1,.,.,.,2,0,1,3253.2051039704647,8.087,0,0,5,1,1,500,500,3,0,0,1,40,5,54.3,115,1,0 +546,55,1,4.304,4,4.304,4.304,1,1,.,.,.,2,0,1,1479.2145772588347,7.299,0,0,5,1,1,500,500,3,0,0,1,40,5,54.3,115,1,0 +547,55,1,7.541,8,7.541,7.541,1,1,.,.,.,2,0,1,585.9203449388576,6.373,0,0,5,1,1,500,500,3,0,0,1,40,5,54.3,115,1,0 +548,55,1,10.97,12,10.97,10.97,1,1,.,.,.,2,0,1,270.3560496775269,5.6,0,0,5,1,1,500,500,3,0,0,1,40,5,54.3,115,1,0 +549,55,1,25.005,24,25.005,25.005,2,1,.,.,.,2,0,1,47.07898307906825,3.852,0,0,5,1,1,500,500,3,0,0,1,40,5,54.3,115,1,0 +550,55,1,48.398,48,48.398,48.398,3,2,.,.,.,2,0,1,6.903995528243134,1.932,0,0,5,1,1,500,500,3,0,0,1,40,5,54.3,115,1,0 +551,56,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,1,3,21,5,99.7,77,2,1 +552,56,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,1,3,21,5,99.7,77,2,0 +553,56,1,0.243,0.25,0.243,0.243,1,1,.,.,.,2,0,1,946.709690029593,6.853,0,0,5,1,1,500,500,3,0,1,3,21,5,99.7,77,2,0 +554,56,1,0.519,0.5,0.519,0.519,1,1,.,.,.,2,0,1,1680.8649936506856,7.427,0,0,5,1,1,500,500,3,0,1,3,21,5,99.7,77,2,0 +555,56,1,0.966,1,0.966,0.966,1,1,.,.,.,2,0,1,1193.9148219311571,7.085,0,0,5,1,1,500,500,3,0,1,3,21,5,99.7,77,2,0 +556,56,1,3.684,4,3.684,3.684,1,1,.,.,.,2,0,1,2072.8490279950015,7.637,0,0,5,1,1,500,500,3,0,1,3,21,5,99.7,77,2,0 +557,56,1,8.634,8,8.634,8.634,1,1,.,.,.,2,0,1,917.6149613328456,6.822,0,0,5,1,1,500,500,3,0,1,3,21,5,99.7,77,2,0 +558,56,1,11.631,12,11.631,11.631,1,1,.,.,.,2,0,1,745.3643553874646,6.614,0,0,5,1,1,500,500,3,0,1,3,21,5,99.7,77,2,0 +559,56,1,24.472,24,24.472,24.472,2,1,.,.,.,2,0,1,335.7138940651394,5.816,0,0,5,1,1,500,500,3,0,1,3,21,5,99.7,77,2,0 +560,56,1,51.287,48,51.287,51.287,3,2,.,.,.,2,0,1,61.11965857174127,4.113,0,0,5,1,1,500,500,3,0,1,3,21,5,99.7,77,2,0 +561,57,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,1,3,36,5,80.8,77,2,1 +562,57,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,1,3,36,5,80.8,77,2,0 +563,57,1,0.267,0.25,0.267,0.267,1,1,.,.,.,2,0,1,1123.286470798349,7.024,0,0,5,1,1,500,500,3,0,1,3,36,5,80.8,77,2,0 +564,57,1,0.499,0.5,0.499,0.499,1,1,.,.,.,2,0,1,785.7262355711418,6.667,0,0,5,1,1,500,500,3,0,1,3,36,5,80.8,77,2,0 +565,57,1,0.972,1,0.972,0.972,1,1,.,.,.,2,0,1,2877.0707198232794,7.965,0,0,5,1,1,500,500,3,0,1,3,36,5,80.8,77,2,0 +566,57,1,3.688,4,3.688,3.688,1,1,.,.,.,2,0,1,1574.8750965012844,7.362,0,0,5,1,1,500,500,3,0,1,3,36,5,80.8,77,2,0 +567,57,1,8.054,8,8.054,8.054,1,1,.,.,.,2,0,1,1748.621423777175,7.467,0,0,5,1,1,500,500,3,0,1,3,36,5,80.8,77,2,0 +568,57,1,11,12,11,11,1,1,.,.,.,2,0,1,1095.916342499254,6.999,0,0,5,1,1,500,500,3,0,1,3,36,5,80.8,77,2,0 +569,57,1,24.933,24,24.933,24.933,2,1,.,.,.,2,0,1,394.97841383953994,5.979,0,0,5,1,1,500,500,3,0,1,3,36,5,80.8,77,2,0 +570,57,1,49.689,48,49.689,49.689,3,2,.,.,.,2,0,1,93.19307868160631,4.535,0,0,5,1,1,500,500,3,0,1,3,36,5,80.8,77,2,0 +571,58,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,1,3,60,5,63.8,84,2,1 +572,58,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,1,3,60,5,63.8,84,2,0 +573,58,1,0.255,0.25,0.255,0.255,1,1,.,.,.,2,0,1,1913.7772315664256,7.557,0,0,5,1,1,500,500,3,0,1,3,60,5,63.8,84,2,0 +574,58,1,0.547,0.5,0.547,0.547,1,1,.,.,.,2,0,1,2581.0915853161264,7.856,0,0,5,1,1,500,500,3,0,1,3,60,5,63.8,84,2,0 +575,58,1,1.035,1,1.035,1.035,1,1,.,.,.,2,0,1,3597.9389064354878,8.188,0,0,5,1,1,500,500,3,0,1,3,60,5,63.8,84,2,0 +576,58,1,4.085,4,4.085,4.085,1,1,.,.,.,2,0,1,1811.2144676641924,7.502,0,0,5,1,1,500,500,3,0,1,3,60,5,63.8,84,2,0 +577,58,1,7.585,8,7.585,7.585,1,1,.,.,.,2,0,1,1472.6453473176814,7.295,0,0,5,1,1,500,500,3,0,1,3,60,5,63.8,84,2,0 +578,58,1,12.481,12,12.481,12.481,1,1,.,.,.,2,0,1,698.9733789061412,6.55,0,0,5,1,1,500,500,3,0,1,3,60,5,63.8,84,2,0 +579,58,1,22.316,24,22.316,22.316,1,1,.,.,.,2,0,1,276.5146575515583,5.622,0,0,5,1,1,500,500,3,0,1,3,60,5,63.8,84,2,0 +580,58,1,46.448,48,46.448,46.448,2,2,.,.,.,2,0,1,57.33944072192072,4.049,0,0,5,1,1,500,500,3,0,1,3,60,5,63.8,84,2,0 +581,59,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,0,1,60,5,66.9,109,1,1 +582,59,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,0,1,60,5,66.9,109,1,0 +583,59,1,0.241,0.25,0.241,0.241,1,1,.,.,.,2,0,1,628.5393534237314,6.443,0,0,5,1,1,500,500,3,0,0,1,60,5,66.9,109,1,0 +584,59,1,0.485,0.5,0.485,0.485,1,1,.,.,.,2,0,1,1065.7579510391938,6.971,0,0,5,1,1,500,500,3,0,0,1,60,5,66.9,109,1,0 +585,59,1,0.957,1,0.957,0.957,1,1,.,.,.,2,0,1,1395.864194306801,7.241,0,0,5,1,1,500,500,3,0,0,1,60,5,66.9,109,1,0 +586,59,1,4.18,4,4.18,4.18,1,1,.,.,.,2,0,1,1344.3388794663667,7.204,0,0,5,1,1,500,500,3,0,0,1,60,5,66.9,109,1,0 +587,59,1,7.572,8,7.572,7.572,1,1,.,.,.,2,0,1,1209.5421854640351,7.098,0,0,5,1,1,500,500,3,0,0,1,60,5,66.9,109,1,0 +588,59,1,11.976,12,11.976,11.976,1,1,.,.,.,2,0,1,603.2815331180017,6.402,0,0,5,1,1,500,500,3,0,0,1,60,5,66.9,109,1,0 +589,59,1,26.036,24,26.036,26.036,2,1,.,.,.,2,0,1,286.1910559286342,5.657,0,0,5,1,1,500,500,3,0,0,1,60,5,66.9,109,1,0 +590,59,1,51.416,48,51.416,51.416,3,2,.,.,.,2,0,1,31.05321158951122,3.436,0,0,5,1,1,500,500,3,0,0,1,60,5,66.9,109,1,0 +591,60,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,1,3,39,5,64,71,2,1 +592,60,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,1,3,39,5,64,71,2,0 +593,60,1,0.269,0.25,0.269,0.269,1,1,.,.,.,2,0,1,1449.3840973088377,7.279,0,0,5,1,1,500,500,3,0,1,3,39,5,64,71,2,0 +594,60,1,0.462,0.5,0.462,0.462,1,1,.,.,.,2,0,1,2191.9543697964996,7.693,0,0,5,1,1,500,500,3,0,1,3,39,5,64,71,2,0 +595,60,1,1.045,1,1.045,1.045,1,1,.,.,.,2,0,1,3895.900739893765,8.268,0,0,5,1,1,500,500,3,0,1,3,39,5,64,71,2,0 +596,60,1,4.287,4,4.287,4.287,1,1,.,.,.,2,0,1,2282.900462994984,7.733,0,0,5,1,1,500,500,3,0,1,3,39,5,64,71,2,0 +597,60,1,7.648,8,7.648,7.648,1,1,.,.,.,2,0,1,1282.6361590571498,7.157,0,0,5,1,1,500,500,3,0,1,3,39,5,64,71,2,0 +598,60,1,11.703,12,11.703,11.703,1,1,.,.,.,2,0,1,1305.6544617912446,7.174,0,0,5,1,1,500,500,3,0,1,3,39,5,64,71,2,0 +599,60,1,25.218,24,25.218,25.218,2,1,.,.,.,2,0,1,463.6491348594561,6.139,0,0,5,1,1,500,500,3,0,1,3,39,5,64,71,2,0 +600,60,1,49.451,48,49.451,49.451,3,2,.,.,.,2,0,1,115.69291130989453,4.751,0,0,5,1,1,500,500,3,0,1,3,39,5,64,71,2,0 +601,61,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,19,5,79.1,73,2,1 +602,61,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,19,5,79.1,73,2,0 +603,61,1,0.241,0.25,0.241,0.241,1,1,.,.,.,2,0,1,907.8837171840179,6.811,0,0,5,1,1,1000,1000,4,0,0,1,19,5,79.1,73,2,0 +604,61,1,0.531,0.5,0.531,0.531,1,1,.,.,.,2,0,1,1578.8430094627006,7.364,0,0,5,1,1,1000,1000,4,0,0,1,19,5,79.1,73,2,0 +605,61,1,0.991,1,0.991,0.991,1,1,.,.,.,2,0,1,2579.6484623974516,7.855,0,0,5,1,1,1000,1000,4,0,0,1,19,5,79.1,73,2,0 +606,61,1,3.756,4,3.756,3.756,1,1,.,.,.,2,0,1,2090.538713488568,7.645,0,0,5,1,1,1000,1000,4,0,0,1,19,5,79.1,73,2,0 +607,61,1,7.287,8,7.287,7.287,1,1,.,.,.,2,0,1,1603.534655687105,7.38,0,0,5,1,1,1000,1000,4,0,0,1,19,5,79.1,73,2,0 +608,61,1,10.875,12,10.875,10.875,1,1,.,.,.,2,0,1,1499.1391836791177,7.313,0,0,5,1,1,1000,1000,4,0,0,1,19,5,79.1,73,2,0 +609,61,1,21.894,24,21.894,21.894,1,1,.,.,.,2,0,1,491.5518799764156,6.198,0,0,5,1,1,1000,1000,4,0,0,1,19,5,79.1,73,2,0 +610,61,1,51.576,48,51.576,51.576,3,2,.,.,.,2,0,1,20.971910676666333,3.043,0,0,5,1,1,1000,1000,4,0,0,1,19,5,79.1,73,2,0 +611,62,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,57,5,70.4,117,1,1 +612,62,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,57,5,70.4,117,1,0 +613,62,1,0.268,0.25,0.268,0.268,1,1,.,.,.,2,0,1,2421.262301924223,7.792,0,0,5,1,1,1000,1000,4,0,0,1,57,5,70.4,117,1,0 +614,62,1,0.478,0.5,0.478,0.478,1,1,.,.,.,2,0,1,3593.3678175797995,8.187,0,0,5,1,1,1000,1000,4,0,0,1,57,5,70.4,117,1,0 +615,62,1,1.017,1,1.017,1.017,1,1,.,.,.,2,0,1,4149.480142179834,8.331,0,0,5,1,1,1000,1000,4,0,0,1,57,5,70.4,117,1,0 +616,62,1,3.647,4,3.647,3.647,1,1,.,.,.,2,0,1,3125.4651060334827,8.047,0,0,5,1,1,1000,1000,4,0,0,1,57,5,70.4,117,1,0 +617,62,1,8.582,8,8.582,8.582,1,1,.,.,.,2,0,1,1454.8308059059584,7.283,0,0,5,1,1,1000,1000,4,0,0,1,57,5,70.4,117,1,0 +618,62,1,11.031,12,11.031,11.031,1,1,.,.,.,2,0,1,836.9548141010805,6.73,0,0,5,1,1,1000,1000,4,0,0,1,57,5,70.4,117,1,0 +619,62,1,23.909,24,23.909,23.909,1,1,.,.,.,2,0,1,311.87476463900873,5.743,0,0,5,1,1,1000,1000,4,0,0,1,57,5,70.4,117,1,0 +620,62,1,48.422,48,48.422,48.422,3,2,.,.,.,2,0,1,37.034885555325914,3.612,0,0,5,1,1,1000,1000,4,0,0,1,57,5,70.4,117,1,0 +621,63,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,55,5,52.1,91,1,1 +622,63,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,55,5,52.1,91,1,0 +623,63,1,0.259,0.25,0.259,0.259,1,1,.,.,.,2,0,1,1211.7149158738716,7.1,0,0,5,1,1,1000,1000,4,0,0,1,55,5,52.1,91,1,0 +624,63,1,0.509,0.5,0.509,0.509,1,1,.,.,.,2,0,1,1616.8712588103913,7.388,0,0,5,1,1,1000,1000,4,0,0,1,55,5,52.1,91,1,0 +625,63,1,1.044,1,1.044,1.044,1,1,.,.,.,2,0,1,3112.9369875151956,8.043,0,0,5,1,1,1000,1000,4,0,0,1,55,5,52.1,91,1,0 +626,63,1,3.706,4,3.706,3.706,1,1,.,.,.,2,0,1,2908.1795833212477,7.975,0,0,5,1,1,1000,1000,4,0,0,1,55,5,52.1,91,1,0 +627,63,1,7.494,8,7.494,7.494,1,1,.,.,.,2,0,1,1632.8315140931782,7.398,0,0,5,1,1,1000,1000,4,0,0,1,55,5,52.1,91,1,0 +628,63,1,13.076,12,13.076,13.076,1,1,.,.,.,2,0,1,944.5631098895316,6.851,0,0,5,1,1,1000,1000,4,0,0,1,55,5,52.1,91,1,0 +629,63,1,24.178,24,24.178,24.178,2,1,.,.,.,2,0,1,351.7556955123468,5.863,0,0,5,1,1,1000,1000,4,0,0,1,55,5,52.1,91,1,0 +630,63,1,50.134,48,50.134,50.134,3,2,.,.,.,2,0,1,37.10579267840746,3.614,0,0,5,1,1,1000,1000,4,0,0,1,55,5,52.1,91,1,0 +631,64,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,52,5,41.3,106,1,1 +632,64,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,52,5,41.3,106,1,0 +633,64,1,0.232,0.25,0.232,0.232,1,1,.,.,.,2,0,1,5247.688051177523,8.566,0,0,5,1,1,1000,1000,4,0,0,1,52,5,41.3,106,1,0 +634,64,1,0.514,0.5,0.514,0.514,1,1,.,.,.,2,0,1,3504.63598851183,8.162,0,0,5,1,1,1000,1000,4,0,0,1,52,5,41.3,106,1,0 +635,64,1,0.936,1,0.936,0.936,1,1,.,.,.,2,0,1,5727.597773408317,8.653,0,0,5,1,1,1000,1000,4,0,0,1,52,5,41.3,106,1,0 +636,64,1,3.716,4,3.716,3.716,1,1,.,.,.,2,0,1,5210.379048827275,8.558,0,0,5,1,1,1000,1000,4,0,0,1,52,5,41.3,106,1,0 +637,64,1,7.796,8,7.796,7.796,1,1,.,.,.,2,0,1,2118.954702359241,7.659,0,0,5,1,1,1000,1000,4,0,0,1,52,5,41.3,106,1,0 +638,64,1,12.369,12,12.369,12.369,1,1,.,.,.,2,0,1,1865.598116297122,7.531,0,0,5,1,1,1000,1000,4,0,0,1,52,5,41.3,106,1,0 +639,64,1,23.706,24,23.706,23.706,1,1,.,.,.,2,0,1,642.997998139496,6.466,0,0,5,1,1,1000,1000,4,0,0,1,52,5,41.3,106,1,0 +640,64,1,48.713,48,48.713,48.713,3,2,.,.,.,2,0,1,69.7448364258404,4.245,0,0,5,1,1,1000,1000,4,0,0,1,52,5,41.3,106,1,0 +641,65,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,33,5,53.1,101,1,1 +642,65,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,33,5,53.1,101,1,0 +643,65,1,0.265,0.25,0.265,0.265,1,1,.,.,.,2,0,1,2337.3846284444994,7.757,0,0,5,1,1,1000,1000,4,0,0,1,33,5,53.1,101,1,0 +644,65,1,0.547,0.5,0.547,0.547,1,1,.,.,.,2,0,1,2398.3655419519923,7.783,0,0,5,1,1,1000,1000,4,0,0,1,33,5,53.1,101,1,0 +645,65,1,1.079,1,1.079,1.079,1,1,.,.,.,2,0,1,4871.044986308018,8.491,0,0,5,1,1,1000,1000,4,0,0,1,33,5,53.1,101,1,0 +646,65,1,4.271,4,4.271,4.271,1,1,.,.,.,2,0,1,2103.555539850791,7.651,0,0,5,1,1,1000,1000,4,0,0,1,33,5,53.1,101,1,0 +647,65,1,7.5,8,7.5,7.5,1,1,.,.,.,2,0,1,2401.2362853248655,7.784,0,0,5,1,1,1000,1000,4,0,0,1,33,5,53.1,101,1,0 +648,65,1,11.2,12,11.2,11.2,1,1,.,.,.,2,0,1,1694.851829358077,7.435,0,0,5,1,1,1000,1000,4,0,0,1,33,5,53.1,101,1,0 +649,65,1,25.688,24,25.688,25.688,2,1,.,.,.,2,0,1,497.9472706982106,6.21,0,0,5,1,1,1000,1000,4,0,0,1,33,5,53.1,101,1,0 +650,65,1,44.17,48,44.17,44.17,2,2,.,.,.,2,0,1,103.16130892778757,4.636,0,0,5,1,1,1000,1000,4,0,0,1,33,5,53.1,101,1,0 +651,66,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,1,3,60,5,92.7,72,2,1 +652,66,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,1,3,60,5,92.7,72,2,0 +653,66,1,0.238,0.25,0.238,0.238,1,1,.,.,.,2,0,1,814.5759987602264,6.703,0,0,5,1,1,1000,1000,4,0,1,3,60,5,92.7,72,2,0 +654,66,1,0.527,0.5,0.527,0.527,1,1,.,.,.,2,0,1,2847.497681630019,7.954,0,0,5,1,1,1000,1000,4,0,1,3,60,5,92.7,72,2,0 +655,66,1,0.944,1,0.944,0.944,1,1,.,.,.,2,0,1,4556.419702619436,8.424,0,0,5,1,1,1000,1000,4,0,1,3,60,5,92.7,72,2,0 +656,66,1,3.761,4,3.761,3.761,1,1,.,.,.,2,0,1,3757.518938011308,8.232,0,0,5,1,1,1000,1000,4,0,1,3,60,5,92.7,72,2,0 +657,66,1,8.583,8,8.583,8.583,1,1,.,.,.,2,0,1,1668.5042356988292,7.42,0,0,5,1,1,1000,1000,4,0,1,3,60,5,92.7,72,2,0 +658,66,1,12.468,12,12.468,12.468,1,1,.,.,.,2,0,1,854.3149435232458,6.75,0,0,5,1,1,1000,1000,4,0,1,3,60,5,92.7,72,2,0 +659,66,1,21.632,24,21.632,21.632,1,1,.,.,.,2,0,1,1127.5927212165743,7.028,0,0,5,1,1,1000,1000,4,0,1,3,60,5,92.7,72,2,0 +660,66,1,45.772,48,45.772,45.772,2,2,.,.,.,2,0,1,396.7876391229974,5.983,0,0,5,1,1,1000,1000,4,0,1,3,60,5,92.7,72,2,0 +661,67,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,1,3,26,5,77.2,81,2,1 +662,67,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,1,3,26,5,77.2,81,2,0 +663,67,1,0.265,0.25,0.265,0.265,1,1,.,.,.,2,0,1,2298.4831358584242,7.74,0,0,5,1,1,1000,1000,4,0,1,3,26,5,77.2,81,2,0 +664,67,1,0.495,0.5,0.495,0.495,1,1,.,.,.,2,0,1,4286.497529449689,8.363,0,0,5,1,1,1000,1000,4,0,1,3,26,5,77.2,81,2,0 +665,67,1,1.005,1,1.005,1.005,1,1,.,.,.,2,0,1,3241.257755705435,8.084,0,0,5,1,1,1000,1000,4,0,1,3,26,5,77.2,81,2,0 +666,67,1,4.282,4,4.282,4.282,1,1,.,.,.,2,0,1,3293.6856648318853,8.1,0,0,5,1,1,1000,1000,4,0,1,3,26,5,77.2,81,2,0 +667,67,1,8.6,8,8.6,8.6,1,1,.,.,.,2,0,1,3515.464782769831,8.165,0,0,5,1,1,1000,1000,4,0,1,3,26,5,77.2,81,2,0 +668,67,1,12.54,12,12.54,12.54,1,1,.,.,.,2,0,1,1462.522836833489,7.288,0,0,5,1,1,1000,1000,4,0,1,3,26,5,77.2,81,2,0 +669,67,1,22.57,24,22.57,22.57,1,1,.,.,.,2,0,1,1048.6397524003173,6.955,0,0,5,1,1,1000,1000,4,0,1,3,26,5,77.2,81,2,0 +670,67,1,43.799,48,43.799,43.799,2,2,.,.,.,2,0,1,343.43617543513864,5.839,0,0,5,1,1,1000,1000,4,0,1,3,26,5,77.2,81,2,0 +671,68,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,29,5,72.1,72,2,1 +672,68,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,29,5,72.1,72,2,0 +673,68,1,0.227,0.25,0.227,0.227,1,1,.,.,.,2,0,1,1554.2826694561138,7.349,0,0,5,1,1,1000,1000,4,0,0,1,29,5,72.1,72,2,0 +674,68,1,0.469,0.5,0.469,0.469,1,1,.,.,.,2,0,1,2928.169375815853,7.982,0,0,5,1,1,1000,1000,4,0,0,1,29,5,72.1,72,2,0 +675,68,1,0.953,1,0.953,0.953,1,1,.,.,.,2,0,1,4204.931770553052,8.344,0,0,5,1,1,1000,1000,4,0,0,1,29,5,72.1,72,2,0 +676,68,1,3.931,4,3.931,3.931,1,1,.,.,.,2,0,1,3716.039796660728,8.22,0,0,5,1,1,1000,1000,4,0,0,1,29,5,72.1,72,2,0 +677,68,1,8.587,8,8.587,8.587,1,1,.,.,.,2,0,1,1582.1203981844467,7.367,0,0,5,1,1,1000,1000,4,0,0,1,29,5,72.1,72,2,0 +678,68,1,12.436,12,12.436,12.436,1,1,.,.,.,2,0,1,1129.9382261500955,7.03,0,0,5,1,1,1000,1000,4,0,0,1,29,5,72.1,72,2,0 +679,68,1,24.881,24,24.881,24.881,2,1,.,.,.,2,0,1,493.3389241209799,6.201,0,0,5,1,1,1000,1000,4,0,0,1,29,5,72.1,72,2,0 +680,68,1,46.005,48,46.005,46.005,2,2,.,.,.,2,0,1,79.25056969077485,4.373,0,0,5,1,1,1000,1000,4,0,0,1,29,5,72.1,72,2,0 +681,69,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,28,5,65.9,112,1,1 +682,69,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,28,5,65.9,112,1,0 +683,69,1,0.236,0.25,0.236,0.236,1,1,.,.,.,2,0,1,1470.281006300334,7.293,0,0,5,1,1,1000,1000,4,0,0,1,28,5,65.9,112,1,0 +684,69,1,0.458,0.5,0.458,0.458,1,1,.,.,.,2,0,1,2577.4638468687162,7.855,0,0,5,1,1,1000,1000,4,0,0,1,28,5,65.9,112,1,0 +685,69,1,0.962,1,0.962,0.962,1,1,.,.,.,2,0,1,2650.9593590301556,7.883,0,0,5,1,1,1000,1000,4,0,0,1,28,5,65.9,112,1,0 +686,69,1,4.285,4,4.285,4.285,1,1,.,.,.,2,0,1,4480.264586424101,8.407,0,0,5,1,1,1000,1000,4,0,0,1,28,5,65.9,112,1,0 +687,69,1,8.194,8,8.194,8.194,1,1,.,.,.,2,0,1,2277.435810039123,7.731,0,0,5,1,1,1000,1000,4,0,0,1,28,5,65.9,112,1,0 +688,69,1,11.322,12,11.322,11.322,1,1,.,.,.,2,0,1,1588.9965931795466,7.371,0,0,5,1,1,1000,1000,4,0,0,1,28,5,65.9,112,1,0 +689,69,1,22.575,24,22.575,22.575,1,1,.,.,.,2,0,1,586.2914132923653,6.374,0,0,5,1,1,1000,1000,4,0,0,1,28,5,65.9,112,1,0 +690,69,1,47.824,48,47.824,47.824,2,2,.,.,.,2,0,1,98.72608801426513,4.592,0,0,5,1,1,1000,1000,4,0,0,1,28,5,65.9,112,1,0 +691,70,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,30,5,91.1,69,2,1 +692,70,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,30,5,91.1,69,2,0 +693,70,1,0.244,0.25,0.244,0.244,1,1,.,.,.,2,0,1,776.1864030888195,6.654,0,0,5,1,1,1000,1000,4,0,0,1,30,5,91.1,69,2,0 +694,70,1,0.472,0.5,0.472,0.472,1,1,.,.,.,2,0,1,3019.9746028545896,8.013,0,0,5,1,1,1000,1000,4,0,0,1,30,5,91.1,69,2,0 +695,70,1,0.98,1,0.98,0.98,1,1,.,.,.,2,0,1,2380.93465353306,7.775,0,0,5,1,1,1000,1000,4,0,0,1,30,5,91.1,69,2,0 +696,70,1,3.782,4,3.782,3.782,1,1,.,.,.,2,0,1,2610.962931912519,7.867,0,0,5,1,1,1000,1000,4,0,0,1,30,5,91.1,69,2,0 +697,70,1,7.583,8,7.583,7.583,1,1,.,.,.,2,0,1,1101.3011411304615,7.004,0,0,5,1,1,1000,1000,4,0,0,1,30,5,91.1,69,2,0 +698,70,1,13.036,12,13.036,13.036,1,1,.,.,.,2,0,1,461.8141940007712,6.135,0,0,5,1,1,1000,1000,4,0,0,1,30,5,91.1,69,2,0 +699,70,1,24.671,24,24.671,24.671,2,1,.,.,.,2,0,1,279.99077417584317,5.635,0,0,5,1,1,1000,1000,4,0,0,1,30,5,91.1,69,2,0 +700,70,1,45.281,48,45.281,45.281,2,2,.,.,.,2,0,1,88.56866305922377,4.484,0,0,5,1,1,1000,1000,4,0,0,1,30,5,91.1,69,2,0 +701,71,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,39,5,78.7,94,1,1 +702,71,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,39,5,78.7,94,1,0 +703,71,1,0.238,0.25,0.238,0.238,1,1,.,.,.,2,0,1,1847.9929690503093,7.522,0,0,5,1,1,1000,1000,4,0,0,1,39,5,78.7,94,1,0 +704,71,1,0.475,0.5,0.475,0.475,1,1,.,.,.,2,0,1,2831.933884200879,7.949,0,0,5,1,1,1000,1000,4,0,0,1,39,5,78.7,94,1,0 +705,71,1,1.024,1,1.024,1.024,1,1,.,.,.,2,0,1,4497.25570558864,8.411,0,0,5,1,1,1000,1000,4,0,0,1,39,5,78.7,94,1,0 +706,71,1,3.989,4,3.989,3.989,1,1,.,.,.,2,0,1,3236.9157243988097,8.082,0,0,5,1,1,1000,1000,4,0,0,1,39,5,78.7,94,1,0 +707,71,1,8.767,8,8.767,8.767,1,1,.,.,.,2,0,1,2128.5766624774474,7.663,0,0,5,1,1,1000,1000,4,0,0,1,39,5,78.7,94,1,0 +708,71,1,13.002,12,13.002,13.002,1,1,.,.,.,2,0,1,1084.7092310780572,6.989,0,0,5,1,1,1000,1000,4,0,0,1,39,5,78.7,94,1,0 +709,71,1,25.75,24,25.75,25.75,2,1,.,.,.,2,0,1,791.3971841667451,6.674,0,0,5,1,1,1000,1000,4,0,0,1,39,5,78.7,94,1,0 +710,71,1,46.245,48,46.245,46.245,2,2,.,.,.,2,0,1,247.4055328965615,5.511,0,0,5,1,1,1000,1000,4,0,0,1,39,5,78.7,94,1,0 +711,72,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,1,3,52,5,90.4,89,2,1 +712,72,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,1,3,52,5,90.4,89,2,0 +713,72,1,0.237,0.25,0.237,0.237,1,1,.,.,.,2,0,1,1233.7904760892004,7.118,0,0,5,1,1,1000,1000,4,0,1,3,52,5,90.4,89,2,0 +714,72,1,0.452,0.5,0.452,0.452,1,1,.,.,.,2,0,1,1853.929738355565,7.525,0,0,5,1,1,1000,1000,4,0,1,3,52,5,90.4,89,2,0 +715,72,1,1.028,1,1.028,1.028,1,1,.,.,.,2,0,1,2789.8765299433944,7.934,0,0,5,1,1,1000,1000,4,0,1,3,52,5,90.4,89,2,0 +716,72,1,4.162,4,4.162,4.162,1,1,.,.,.,2,0,1,3022.5754657936845,8.014,0,0,5,1,1,1000,1000,4,0,1,3,52,5,90.4,89,2,0 +717,72,1,7.534,8,7.534,7.534,1,1,.,.,.,2,0,1,2866.5584916841326,7.961,0,0,5,1,1,1000,1000,4,0,1,3,52,5,90.4,89,2,0 +718,72,1,12.635,12,12.635,12.635,1,1,.,.,.,2,0,1,2058.1998477724833,7.63,0,0,5,1,1,1000,1000,4,0,1,3,52,5,90.4,89,2,0 +719,72,1,25.866,24,25.866,25.866,2,1,.,.,.,2,0,1,508.1160341737293,6.231,0,0,5,1,1,1000,1000,4,0,1,3,52,5,90.4,89,2,0 +720,72,1,51.991,48,51.991,51.991,3,2,.,.,.,2,0,1,149.12318128239798,5.005,0,0,5,1,1,1000,1000,4,0,1,3,52,5,90.4,89,2,0 +721,73,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,1,3,53,5,78.9,72,2,1 +722,73,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,1,3,53,5,78.9,72,2,0 +723,73,1,0.235,0.25,0.235,0.235,1,1,.,.,.,2,0,1,1224.2606886919743,7.11,0,0,5,1,1,1000,1000,4,0,1,3,53,5,78.9,72,2,0 +724,73,1,0.516,0.5,0.516,0.516,1,1,.,.,.,2,0,1,2020.3363829118796,7.611,0,0,5,1,1,1000,1000,4,0,1,3,53,5,78.9,72,2,0 +725,73,1,0.993,1,0.993,0.993,1,1,.,.,.,2,0,1,4449.245197070604,8.4,0,0,5,1,1,1000,1000,4,0,1,3,53,5,78.9,72,2,0 +726,73,1,3.682,4,3.682,3.682,1,1,.,.,.,2,0,1,3254.676859562131,8.088,0,0,5,1,1,1000,1000,4,0,1,3,53,5,78.9,72,2,0 +727,73,1,8.62,8,8.62,8.62,1,1,.,.,.,2,0,1,2293.244727802458,7.738,0,0,5,1,1,1000,1000,4,0,1,3,53,5,78.9,72,2,0 +728,73,1,12.649,12,12.649,12.649,1,1,.,.,.,2,0,1,1015.378527428847,6.923,0,0,5,1,1,1000,1000,4,0,1,3,53,5,78.9,72,2,0 +729,73,1,24.792,24,24.792,24.792,2,1,.,.,.,2,0,1,461.8442853547288,6.135,0,0,5,1,1,1000,1000,4,0,1,3,53,5,78.9,72,2,0 +730,73,1,44.395,48,44.395,44.395,2,2,.,.,.,2,0,1,115.56337388107191,4.75,0,0,5,1,1,1000,1000,4,0,1,3,53,5,78.9,72,2,0 +731,74,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,1,3,21,5,111.5,77,2,1 +732,74,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,1,3,21,5,111.5,77,2,0 +733,74,1,0.253,0.25,0.253,0.253,1,1,.,.,.,2,0,1,1332.9577666991793,7.195,0,0,5,1,1,1000,1000,4,0,1,3,21,5,111.5,77,2,0 +734,74,1,0.497,0.5,0.497,0.497,1,1,.,.,.,2,0,1,2431.0823735274057,7.796,0,0,5,1,1,1000,1000,4,0,1,3,21,5,111.5,77,2,0 +735,74,1,1.033,1,1.033,1.033,1,1,.,.,.,2,0,1,2909.181828060483,7.976,0,0,5,1,1,1000,1000,4,0,1,3,21,5,111.5,77,2,0 +736,74,1,4.201,4,4.201,4.201,1,1,.,.,.,2,0,1,3900.9370125857467,8.269,0,0,5,1,1,1000,1000,4,0,1,3,21,5,111.5,77,2,0 +737,74,1,8.436,8,8.436,8.436,1,1,.,.,.,2,0,1,2783.654113828685,7.932,0,0,5,1,1,1000,1000,4,0,1,3,21,5,111.5,77,2,0 +738,74,1,12.634,12,12.634,12.634,1,1,.,.,.,2,0,1,1825.971015296924,7.51,0,0,5,1,1,1000,1000,4,0,1,3,21,5,111.5,77,2,0 +739,74,1,26.223,24,26.223,26.223,2,1,.,.,.,2,0,1,931.4967530242837,6.837,0,0,5,1,1,1000,1000,4,0,1,3,21,5,111.5,77,2,0 +740,74,1,49.735,48,49.735,49.735,3,2,.,.,.,2,0,1,649.495547026835,6.476,0,0,5,1,1,1000,1000,4,0,1,3,21,5,111.5,77,2,0 +741,75,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,20,5,77,78,2,1 +742,75,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,20,5,77,78,2,0 +743,75,1,0.256,0.25,0.256,0.256,1,1,.,.,.,2,0,1,1132.8423244472767,7.032,0,0,5,1,1,1000,1000,4,0,0,1,20,5,77,78,2,0 +744,75,1,0.518,0.5,0.518,0.518,1,1,.,.,.,2,0,1,1905.760898391289,7.553,0,0,5,1,1,1000,1000,4,0,0,1,20,5,77,78,2,0 +745,75,1,0.931,1,0.931,0.931,1,1,.,.,.,2,0,1,2746.549556564803,7.918,0,0,5,1,1,1000,1000,4,0,0,1,20,5,77,78,2,0 +746,75,1,4.342,4,4.342,4.342,1,1,.,.,.,2,0,1,2894.403560082858,7.971,0,0,5,1,1,1000,1000,4,0,0,1,20,5,77,78,2,0 +747,75,1,8.8,8,8.8,8.8,1,1,.,.,.,2,0,1,2209.0355182822836,7.7,0,0,5,1,1,1000,1000,4,0,0,1,20,5,77,78,2,0 +748,75,1,11.12,12,11.12,11.12,1,1,.,.,.,2,0,1,1336.2595985325108,7.198,0,0,5,1,1,1000,1000,4,0,0,1,20,5,77,78,2,0 +749,75,1,22.11,24,22.11,22.11,1,1,.,.,.,2,0,1,523.8976567399562,6.261,0,0,5,1,1,1000,1000,4,0,0,1,20,5,77,78,2,0 +750,75,1,50.519,48,50.519,50.519,3,2,.,.,.,2,0,1,125.39115876428644,4.831,0,0,5,1,1,1000,1000,4,0,0,1,20,5,77,78,2,0 +751,76,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,26,5,52.3,109,1,1 +752,76,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,26,5,52.3,109,1,0 +753,76,1,0.256,0.25,0.256,0.256,1,1,.,.,.,2,0,1,2716.815649324218,7.907,0,0,5,1,1,1000,1000,4,0,0,1,26,5,52.3,109,1,0 +754,76,1,0.532,0.5,0.532,0.532,1,1,.,.,.,2,0,1,2695.7623081194065,7.899,0,0,5,1,1,1000,1000,4,0,0,1,26,5,52.3,109,1,0 +755,76,1,0.936,1,0.936,0.936,1,1,.,.,.,2,0,1,2509.052888087842,7.828,0,0,5,1,1,1000,1000,4,0,0,1,26,5,52.3,109,1,0 +756,76,1,3.869,4,3.869,3.869,1,1,.,.,.,2,0,1,3107.259044385997,8.041,0,0,5,1,1,1000,1000,4,0,0,1,26,5,52.3,109,1,0 +757,76,1,7.458,8,7.458,7.458,1,1,.,.,.,2,0,1,1819.4903321731474,7.506,0,0,5,1,1,1000,1000,4,0,0,1,26,5,52.3,109,1,0 +758,76,1,12.53,12,12.53,12.53,1,1,.,.,.,2,0,1,1757.9166591076375,7.472,0,0,5,1,1,1000,1000,4,0,0,1,26,5,52.3,109,1,0 +759,76,1,23.647,24,23.647,23.647,1,1,.,.,.,2,0,1,626.0727526851555,6.439,0,0,5,1,1,1000,1000,4,0,0,1,26,5,52.3,109,1,0 +760,76,1,44.834,48,44.834,44.834,2,2,.,.,.,2,0,1,181.41209788516744,5.201,0,0,5,1,1,1000,1000,4,0,0,1,26,5,52.3,109,1,0 +761,77,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,1,3,27,5,88.2,83,2,1 +762,77,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,1,3,27,5,88.2,83,2,0 +763,77,1,0.238,0.25,0.238,0.238,1,1,.,.,.,2,0,1,776.2338644366163,6.654,0,0,5,1,1,1000,1000,4,0,1,3,27,5,88.2,83,2,0 +764,77,1,0.457,0.5,0.457,0.457,1,1,.,.,.,2,0,1,964.4627442019198,6.872,0,0,5,1,1,1000,1000,4,0,1,3,27,5,88.2,83,2,0 +765,77,1,1.087,1,1.087,1.087,1,1,.,.,.,2,0,1,2511.337997759726,7.829,0,0,5,1,1,1000,1000,4,0,1,3,27,5,88.2,83,2,0 +766,77,1,3.981,4,3.981,3.981,1,1,.,.,.,2,0,1,3604.2356046739883,8.19,0,0,5,1,1,1000,1000,4,0,1,3,27,5,88.2,83,2,0 +767,77,1,8.609,8,8.609,8.609,1,1,.,.,.,2,0,1,2494.5585014879166,7.822,0,0,5,1,1,1000,1000,4,0,1,3,27,5,88.2,83,2,0 +768,77,1,11.146,12,11.146,11.146,1,1,.,.,.,2,0,1,2190.755784047479,7.692,0,0,5,1,1,1000,1000,4,0,1,3,27,5,88.2,83,2,0 +769,77,1,23.961,24,23.961,23.961,1,1,.,.,.,2,0,1,899.9433582219998,6.802,0,0,5,1,1,1000,1000,4,0,1,3,27,5,88.2,83,2,0 +770,77,1,47.428,48,47.428,47.428,2,2,.,.,.,2,0,1,167.21659192637665,5.119,0,0,5,1,1,1000,1000,4,0,1,3,27,5,88.2,83,2,0 +771,78,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,1,3,19,5,69.6,114,1,1 +772,78,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,1,3,19,5,69.6,114,1,0 +773,78,1,0.25,0.25,0.25,0.25,1,1,.,.,.,2,0,1,1181.171026636279,7.074,0,0,5,1,1,1000,1000,4,0,1,3,19,5,69.6,114,1,0 +774,78,1,0.506,0.5,0.506,0.506,1,1,.,.,.,2,0,1,3057.1207578876188,8.025,0,0,5,1,1,1000,1000,4,0,1,3,19,5,69.6,114,1,0 +775,78,1,0.976,1,0.976,0.976,1,1,.,.,.,2,0,1,3632.6237326923565,8.198,0,0,5,1,1,1000,1000,4,0,1,3,19,5,69.6,114,1,0 +776,78,1,3.774,4,3.774,3.774,1,1,.,.,.,2,0,1,2596.1369320358026,7.862,0,0,5,1,1,1000,1000,4,0,1,3,19,5,69.6,114,1,0 +777,78,1,7.419,8,7.419,7.419,1,1,.,.,.,2,0,1,1607.107472139053,7.382,0,0,5,1,1,1000,1000,4,0,1,3,19,5,69.6,114,1,0 +778,78,1,13.076,12,13.076,13.076,1,1,.,.,.,2,0,1,1175.673159075275,7.07,0,0,5,1,1,1000,1000,4,0,1,3,19,5,69.6,114,1,0 +779,78,1,21.647,24,21.647,21.647,1,1,.,.,.,2,0,1,484.1178247323139,6.182,0,0,5,1,1,1000,1000,4,0,1,3,19,5,69.6,114,1,0 +780,78,1,48.855,48,48.855,48.855,3,2,.,.,.,2,0,1,65.04579398218401,4.175,0,0,5,1,1,1000,1000,4,0,1,3,19,5,69.6,114,1,0 +781,79,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,38,5,46.4,106,1,1 +782,79,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,38,5,46.4,106,1,0 +783,79,1,0.259,0.25,0.259,0.259,1,1,.,.,.,2,0,1,1706.2082476938087,7.442,0,0,5,1,1,1000,1000,4,0,0,1,38,5,46.4,106,1,0 +784,79,1,0.51,0.5,0.51,0.51,1,1,.,.,.,2,0,1,3683.8036945926683,8.212,0,0,5,1,1,1000,1000,4,0,0,1,38,5,46.4,106,1,0 +785,79,1,0.911,1,0.911,0.911,1,1,.,.,.,2,0,1,1797.0528333048073,7.494,0,0,5,1,1,1000,1000,4,0,0,1,38,5,46.4,106,1,0 +786,79,1,3.612,4,3.612,3.612,1,1,.,.,.,2,0,1,3237.8999724003634,8.083,0,0,5,1,1,1000,1000,4,0,0,1,38,5,46.4,106,1,0 +787,79,1,7.854,8,7.854,7.854,1,1,.,.,.,2,0,1,1935.2569548745782,7.568,0,0,5,1,1,1000,1000,4,0,0,1,38,5,46.4,106,1,0 +788,79,1,11.075,12,11.075,11.075,1,1,.,.,.,2,0,1,1271.5185584722992,7.148,0,0,5,1,1,1000,1000,4,0,0,1,38,5,46.4,106,1,0 +789,79,1,26.216,24,26.216,26.216,2,1,.,.,.,2,0,1,235.5032086502458,5.462,0,0,5,1,1,1000,1000,4,0,0,1,38,5,46.4,106,1,0 +790,79,1,46.972,48,46.972,46.972,2,2,.,.,.,2,0,1,20.881913229987706,3.039,0,0,5,1,1,1000,1000,4,0,0,1,38,5,46.4,106,1,0 +791,80,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,44,5,52.7,83,2,1 +792,80,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,44,5,52.7,83,2,0 +793,80,1,0.232,0.25,0.232,0.232,1,1,.,.,.,2,0,1,1066.8279759953161,6.972,0,0,5,1,1,1000,1000,4,0,0,1,44,5,52.7,83,2,0 +794,80,1,0.542,0.5,0.542,0.542,1,1,.,.,.,2,0,1,4998.834848168029,8.517,0,0,5,1,1,1000,1000,4,0,0,1,44,5,52.7,83,2,0 +795,80,1,1.079,1,1.079,1.079,1,1,.,.,.,2,0,1,6495.09106416749,8.779,0,0,5,1,1,1000,1000,4,0,0,1,44,5,52.7,83,2,0 +796,80,1,4.1,4,4.1,4.1,1,1,.,.,.,2,0,1,4796.368834030873,8.476,0,0,5,1,1,1000,1000,4,0,0,1,44,5,52.7,83,2,0 +797,80,1,7.209,8,7.209,7.209,1,1,.,.,.,2,0,1,3361.382006860858,8.12,0,0,5,1,1,1000,1000,4,0,0,1,44,5,52.7,83,2,0 +798,80,1,13.187,12,13.187,13.187,1,1,.,.,.,2,0,1,2291.7225610342075,7.737,0,0,5,1,1,1000,1000,4,0,0,1,44,5,52.7,83,2,0 +799,80,1,25.84,24,25.84,25.84,2,1,.,.,.,2,0,1,703.0648954331083,6.555,0,0,5,1,1,1000,1000,4,0,0,1,44,5,52.7,83,2,0 +800,80,1,43.822,48,43.822,43.822,2,2,.,.,.,2,0,1,341.66481208668955,5.834,0,0,5,1,1,1000,1000,4,0,0,1,44,5,52.7,83,2,0 diff --git a/inst/extdata/models/1002.mod b/inst/extdata/models/1002.mod new file mode 100644 index 00000000..e69de29b From d8c53109c15f56b73ff807fee0ef6e22a4f3a418 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Thu, 2 Apr 2026 10:21:51 -0400 Subject: [PATCH 04/41] move to nmparser --- _starlightr.toml | 3 +- src/rust/Cargo.lock | 184 +++------- src/rust/Cargo.toml | 6 +- src/rust/nmparser/Cargo.toml | 7 - src/rust/nmparser/src/lib.rs | 173 ++++++++- src/rust/nmparser/src/nmmodel/mod.rs | 98 ----- src/rust/nmparser/src/utils.rs | 462 ------------------------ src/rust/nonmem/Cargo.toml | 1 + src/rust/nonmem/src/model/check.rs | 37 +- src/rust/nonmem/src/model/mod.rs | 130 +------ src/rust/nonmem/src/model/parameters.rs | 6 +- src/rust/nonmem/src/output_files/grd.rs | 4 +- 12 files changed, 281 insertions(+), 830 deletions(-) delete mode 100644 src/rust/nmparser/src/nmmodel/mod.rs delete mode 100644 src/rust/nmparser/src/utils.rs diff --git a/_starlightr.toml b/_starlightr.toml index 4f00cf4f..f93d0959 100644 --- a/_starlightr.toml +++ b/_starlightr.toml @@ -13,7 +13,8 @@ include_pagefind = true [versions] enabled = true list = [ - { tag = "0.4.1", label = "v0.4.1", default = true }, + { tag = "0.4.2", label = "v0.4.2", default = true }, + { tag = "0.4.1", label = "v0.4.1" }, { tag = "0.3.2", label = "v0.3.2" }, { tag = "0.3.1", label = "v0.3.1" }, { tag = "0.3.0", label = "v0.3.0" }, diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index b225d1df..096149ea 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -52,16 +52,16 @@ checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "blake3" -version = "1.8.3" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" +checksum = "4d2d5991425dfd0785aed03aedcf0b321d61975c9b5b3689c774a2610ae0b51e" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", - "cpufeatures", + "cpufeatures 0.3.0", ] [[package]] @@ -91,9 +91,9 @@ checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "cc" -version = "1.2.57" +version = "1.2.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" dependencies = [ "find-msvc-tools", "shlex", @@ -141,13 +141,14 @@ dependencies = [ [[package]] name = "config" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=main#1626fabc79f501530c851a54e484981ba04b78ba" +source = "git+https://github.com/a2-ai/pharos?branch=nonmem-parser2#7d4126739e45d47609572a81c91d606b8b63bf3f" dependencies = [ "anyhow", "fs-err", "glob", "jiff", "log", + "nonmem-parser", "serde", "tera", "toml", @@ -156,14 +157,13 @@ dependencies = [ [[package]] name = "console" -version = "0.15.11" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" dependencies = [ "encode_unicode", "libc", - "once_cell", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -187,6 +187,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -269,7 +278,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -474,15 +483,10 @@ dependencies = [ name = "hyperion-nmparser" version = "0.1.0" dependencies = [ - "config", "extendr-api", "fs-err", "hyperion-core", - "insta", - "nonmem", "nonmem-parser", - "serde", - "tempfile", ] [[package]] @@ -493,6 +497,7 @@ dependencies = [ "extendr-api", "fs-err", "hyperion-core", + "hyperion-nmparser", "insta", "nonmem", "serde", @@ -571,9 +576,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.46.3" +version = "1.47.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82db8c87c7f1ccecb34ce0c24399b8a73081427f3c7c50a5d597925356115e4" +checksum = "7b4a6248eb93a4401ed2f37dfe8ea592d3cf05b7cf4f8efa867b6895af7e094e" dependencies = [ "console", "globset", @@ -601,7 +606,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -632,9 +637,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" dependencies = [ "once_cell", "wasm-bindgen", @@ -654,9 +659,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "libm" @@ -717,7 +722,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "nonmem" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=main#1626fabc79f501530c851a54e484981ba04b78ba" +source = "git+https://github.com/a2-ai/pharos?branch=nonmem-parser2#7d4126739e45d47609572a81c91d606b8b63bf3f" dependencies = [ "anyhow", "blake3", @@ -727,6 +732,7 @@ dependencies = [ "glob", "jiff", "log", + "nonmem-parser", "num_cpus", "rand 0.9.2", "rayon", @@ -743,9 +749,10 @@ dependencies = [ [[package]] name = "nonmem-parser" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=nonmem-parser2#58dcbcf15bedc7feb0cb12d5f50a252bf745c4e9" +source = "git+https://github.com/a2-ai/pharos?branch=nonmem-parser2#7d4126739e45d47609572a81c91d606b8b63bf3f" dependencies = [ "anyhow", + "distrs", "fs-err", "logos", "rand 0.9.2", @@ -1073,7 +1080,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -1094,7 +1101,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=main#1626fabc79f501530c851a54e484981ba04b78ba" +source = "git+https://github.com/a2-ai/pharos?branch=nonmem-parser2#7d4126739e45d47609572a81c91d606b8b63bf3f" dependencies = [ "anyhow", "config", @@ -1156,9 +1163,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] @@ -1170,7 +1177,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -1243,7 +1250,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -1294,18 +1301,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.0+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.0", + "winnow 1.0.1", ] [[package]] name = "toml_writer" -version = "1.1.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "typenum" @@ -1340,7 +1347,7 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utils" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=main#1626fabc79f501530c851a54e484981ba04b78ba" +source = "git+https://github.com/a2-ai/pharos?branch=nonmem-parser2#7d4126739e45d47609572a81c91d606b8b63bf3f" dependencies = [ "anyhow", "fs-err", @@ -1391,9 +1398,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" dependencies = [ "cfg-if", "once_cell", @@ -1404,9 +1411,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1414,9 +1421,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" dependencies = [ "bumpalo", "proc-macro2", @@ -1427,9 +1434,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" dependencies = [ "unicode-ident", ] @@ -1483,7 +1490,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -1545,15 +1552,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -1563,70 +1561,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - [[package]] name = "winnow" version = "0.7.15" @@ -1635,9 +1569,9 @@ checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" [[package]] name = "winnow" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" [[package]] name = "wit-bindgen" @@ -1729,18 +1663,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index af4530e7..e6f3aa34 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -39,10 +39,10 @@ serde = { workspace = true } extendr-api = { git = "https://github.com/extendr/extendr", branch = "main", features = ["serde"] } # Pharos components +nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "nonmem-parser2" } nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", branch = "nonmem-parser2" } -nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "main" } -config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "main" } -scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "main" } +config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "nonmem-parser2" } +scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "nonmem-parser2" } # Core utilities anyhow = "1.0.100" diff --git a/src/rust/nmparser/Cargo.toml b/src/rust/nmparser/Cargo.toml index 9566331c..90a99c04 100644 --- a/src/rust/nmparser/Cargo.toml +++ b/src/rust/nmparser/Cargo.toml @@ -10,13 +10,6 @@ hyperion-core = { path = "../core" } # Pharos components nmparser = {workspace = true} -nonmem = { workspace = true } -config = { workspace = true } # Core utilities fs-err = { workspace = true } -serde = { workspace = true } - -# Testing and utilities -insta = { workspace = true } -tempfile = { workspace = true } diff --git a/src/rust/nmparser/src/lib.rs b/src/rust/nmparser/src/lib.rs index a7d751d4..955694e7 100644 --- a/src/rust/nmparser/src/lib.rs +++ b/src/rust/nmparser/src/lib.rs @@ -1,10 +1,177 @@ +use extendr_api::Result; +use extendr_api::deserializer::from_robj; use extendr_api::prelude::*; +use extendr_api::serializer::to_robj; -pub mod nmmodel; -pub mod utils; +use fs_err as fs; +use std::path::Component; +use std::path::Path; +use std::path::PathBuf; + +use hyperion_core::find_config_dir; +use hyperion_core::{OptionExt, ResultExt, extendr_err}; +use nmparser::Model; + +/// Helper to convert Model to Robj for read_model and other model readers. +pub fn model_to_robj(model: &Model, path: impl AsRef) -> Result { + let path = path.as_ref(); + + let mut model_robj = to_robj(model).map_to_extendr_err("failed to create Robj from Model")?; + + add_filename_attr(&mut model_robj, path)?; + add_model_source_attr(&mut model_robj, path)?; + + set_model_class(&mut model_robj) +} + +fn add_filename_attr(model_robj: &mut Robj, path: &Path) -> Result<()> { + if let Some(n) = path.file_stem().and_then(|name| name.to_str()) { + model_robj + .set_attrib("filename", n.into_robj()) + .map_to_extendr_err("Failed to set filename attribute")?; + } + Ok(()) +} + +fn add_model_source_attr(model_robj: &mut Robj, path: &Path) -> Result<()> { + let source_path = to_config_relative(path)?; + model_robj + .set_attrib("model_source", source_path.into_robj()) + .map_to_extendr_err("Failed to set model source attribute")?; + + Ok(()) +} + +fn set_model_class(model_robj: &mut Robj) -> Result { + let result = model_robj + .set_class(["hyperion_nonmem_model"]) + .map_to_extendr_err("Failed to set class")?; + + Ok(result.to_owned()) +} + +/// Helper function to reconstruct a parser Model from hyperion_nonmem_model Robj +pub fn robj_to_model(model: &Robj) -> Result { + from_robj(model).map_to_extendr_err("Failed to create Model from Robj") +} + +fn validate_model_path(input_path: impl AsRef) -> Result { + let path = input_path.as_ref(); + + if path.is_dir() { + return Err(extendr_err!( + "Expected .mod or .ctl file path, got directory: {}", + path.display() + )); + } + + let ext = match path.extension().and_then(|e| e.to_str()) { + Some("mod") => "mod", + Some("ctl") => "ctl", + _ => { + return Err(extendr_err!( + "Expected .mod or .ctl file path: {}", + path.display() + )); + } + }; + + if path.exists() { + let stem = path + .file_stem() + .ok_or_extendr_err("Could not determine model file stem")? + .to_string_lossy() + .to_string(); + if let Some(parent) = path.parent() + && parent + .file_name() + .and_then(|name| name.to_str()) + .is_some_and(|name| name == stem.as_str()) + { + let candidate = parent.with_extension(ext); + return Err(extendr_err!( + "Expected input model file, got output model file: {}\n\ + Try: {}", + path.display(), + candidate.display() + )); + } + + return Ok(path.to_path_buf()); + } + + Err(extendr_err!("File not found: {}", path.display())) +} + +fn make_relative_path(base: &Path, target: &Path) -> PathBuf { + let base_components: Vec> = base.components().collect(); + let target_components: Vec> = target.components().collect(); + + if base_components.first() != target_components.first() { + return target.to_path_buf(); + } + + let mut idx = 0; + let max = base_components.len().min(target_components.len()); + while idx < max && base_components[idx] == target_components[idx] { + idx += 1; + } + + let mut rel = PathBuf::new(); + for _ in idx..base_components.len() { + rel.push(".."); + } + for comp in target_components.iter().skip(idx) { + rel.push(comp.as_os_str()); + } + + rel +} + +fn to_config_relative(path: impl AsRef) -> Result { + let path = path.as_ref(); + let config_dir = find_config_dir().map_to_extendr_err("Failed to find config dir")?; + + if let Some(dir) = config_dir { + let rel = make_relative_path(&dir, path); + return Ok(rel.to_string_lossy().to_string()); + } + + Ok(path.to_string_lossy().to_string()) +} + +/// Gets model object +/// +/// @param path path to mod or ctl file. +/// +/// @return hyperion_nonmem_model S3 object with `model_source` attribute +/// @export +/// +/// @examples \dontrun{ +/// read_model("model/nonmem/run001.mod") +/// } +#[extendr] +pub fn read_model(path: &str) -> Result { + let mod_path = validate_model_path(path)?; + let content = fs::read_to_string(&mod_path).map_to_extendr_err("")?; + + let model = match Model::parse(&content) { + Ok(model) => model, + Err(diags) => { + let msg = diags + .iter() + .map(|d| d.to_string()) + .collect::>() + .join("\n"); + return Err(extendr_err!("Failed to read model file:\n{msg}")); + } + }; + let robj_model = model_to_robj(&model, mod_path)?; + Ok(robj_model) +} // Generate extendr module for R integration extendr_module! { mod hyperion_nmparser; - use nmmodel; + fn read_model; } diff --git a/src/rust/nmparser/src/nmmodel/mod.rs b/src/rust/nmparser/src/nmmodel/mod.rs deleted file mode 100644 index 7aa64b10..00000000 --- a/src/rust/nmparser/src/nmmodel/mod.rs +++ /dev/null @@ -1,98 +0,0 @@ -use extendr_api::Result; -use extendr_api::deserializer::from_robj; -use extendr_api::prelude::*; -use extendr_api::serializer::to_robj; - -use fs_err as fs; -use std::path::Path; - -//pharos nonmem crate -use nmparser::Model; - -use crate::utils::{to_config_relative, validate_model_path}; -use hyperion_core::ResultExt; - -/// Helper to convert Model to Robj for read_model and read_model_from_lst -/// -/// This handles comment parsing and Model -> Robj + S3 setting -fn model_to_robj(model: &mut Model, path: impl AsRef) -> Result { - let path = path.as_ref(); - - let mut model_robj = to_robj(&model).map_to_extendr_err("failed to create Robj from Model")?; - - add_filename_attr(&mut model_robj, path)?; - add_model_source_attr(&mut model_robj, path)?; - add_run_status_attr(&mut model_robj, path)?; - - set_model_class(&mut model_robj) -} - -fn add_filename_attr(model_robj: &mut Robj, path: &Path) -> Result<()> { - if let Some(n) = path.file_stem().and_then(|name| name.to_str()) { - model_robj - .set_attrib("filename", n.into_robj()) - .map_to_extendr_err("Failed to set filename attribute")?; - } - Ok(()) -} - -fn add_model_source_attr(model_robj: &mut Robj, path: &Path) -> Result<()> { - let source_path = to_config_relative(path)?; - model_robj - .set_attrib("model_source", source_path.into_robj()) - .map_to_extendr_err("Failed to set model source attribute")?; - - Ok(()) -} - -fn add_run_status_attr(model_robj: &mut Robj, path: &Path) -> Result<()> { - if let Some(ext) = path.extension().and_then(|e| e.to_str()) - && (ext == "mod" || ext == "ctl" || ext == "lst") - { - let run_status = "run"; - model_robj - .set_attrib("run_status", run_status.to_string().into_robj()) - .map_to_extendr_err("Failed to set run_status attribute")?; - } - - Ok(()) -} - -fn set_model_class(model_robj: &mut Robj) -> Result { - let result = model_robj - .set_class(["hyperion_nonmem_nmmodel"]) - .map_to_extendr_err("Failed to set class")?; - - Ok(result.to_owned()) -} - -/// Helper function to reconstruct a pharos Model from hyperion_nonmem_model Robj -pub fn robj_to_model(model: &Robj) -> Result { - from_robj(model).map_to_extendr_err("Failed to create Model from Robj") -} - -/// Gets model object -/// -/// @param path path to mod or ctl file. -/// -/// @return hyperion_nonmem_model S3 object with `model_source` and `run_status` attributes -/// @export -/// -/// @examples \dontrun{ -/// read_nmmodel("model/nonmem/run001.mod") -/// } -#[extendr] -pub fn read_nmmodel(path: &str) -> Result { - let mod_path = validate_model_path(path)?; - let content = fs::read_to_string(&mod_path).map_to_extendr_err("")?; - - let (mut model, _diagonstics) = - Model::parse(&content).map_to_extendr_err("Failed to read model file")?; - let robj_model = model_to_robj(&mut model, mod_path)?; - Ok(robj_model) -} - -extendr_module! { - mod nmmodel; - fn read_nmmodel; -} diff --git a/src/rust/nmparser/src/utils.rs b/src/rust/nmparser/src/utils.rs deleted file mode 100644 index 87555968..00000000 --- a/src/rust/nmparser/src/utils.rs +++ /dev/null @@ -1,462 +0,0 @@ -use extendr_api::Result; -use extendr_api::prelude::*; - -use fs_err as fs; -use std::path::Component; -use std::path::{Path, PathBuf}; - -// pharos config and nonmem crate -use config::{CONFIG_FILENAME, CommentType, Config, NonmemConfig}; -use nmparser::Model; - -// hyperion core -use hyperion_core::{OptionExt, ResultExt, extendr_err, find_config_dir}; - -/// Finds the correct output file path with the specified extension -/// -/// This function handles various input formats and locates the expected -/// NONMEM output file location following the standard directory structure. -/// -/// # Examples: -/// ``` -/// # use hyperion_nonmem::utils::find_output_file; -/// // Directory input returns expected path -/// let result = find_output_file("models/run001", "ext"); -/// // Should return "models/run001/run001.ext" -/// -/// // .mod file input returns expected path -/// let result = find_output_file("models/run001.mod", "ext"); -/// // Should return "models/run001/run001.ext" -/// ``` -/// -/// # Arguments: -/// * `input_path` - The input path (directory, .mod file, or output file) -/// * `extension` - The desired file extension (without dot, e.g. "ext", "grd") -/// -/// # Returns: -/// * `Ok(PathBuf)` - The path to the output file -/// * `Err(Error)` - If the output file doesn't exist -pub fn find_output_file(input_path: impl AsRef, extension: &str) -> Result { - let path = input_path.as_ref(); - - // If the input already has the target extension, check if it exists - if let Some(current_ext) = path.extension() - && current_ext == extension - { - if path.exists() { - return Ok(path.to_path_buf()); - } else { - return Err(extendr_err!("File not found: {}", path.display())); - } - } - // Determine the base name for the output file - let basename = if path.is_dir() { - // Directory input: use directory name as basename - path.file_name() - .ok_or_extendr_err("Cannot determine directory name")? - .to_string_lossy() - .to_string() - } else { - // File input: use file stem as basename, handling special cases - let stem = path - .file_stem() - .ok_or_extendr_err("Cannot determine file stem")? - .to_string_lossy(); - - // Handle metadata files: run001_metadata.json -> run001 - if stem.ends_with("_metadata") { - stem.strip_suffix("_metadata").unwrap().to_string() - } else { - stem.to_string() - } - }; - - // Construct the expected output file path - let output_path = if path.is_dir() { - // Directory/basename/basename.ext - path.join(format!("{}.{}", basename, extension)) - } else { - // parent/basename/basename.ext - let parent = path - .parent() - .ok_or_extendr_err("Cannot determine parent directory")?; - parent - .join(&basename) - .join(format!("{}.{}", basename, extension)) - }; - - // Verify the output file exists - if output_path.exists() { - Ok(output_path) - } else { - Err(extendr_err!( - "Output file not found: {}\nExpected location based on input: {}", - output_path.display(), - path.display() - )) - } -} - -/// Validate and resolve a model input path (.mod or .ctl). -/// Returns error if path is not a .mod/.ctl file or doesn't exist. -pub fn validate_model_path(input_path: impl AsRef) -> Result { - let path = input_path.as_ref(); - - if path.is_dir() { - return Err(extendr_err!( - "Expected .mod or .ctl file path, got directory: {}", - path.display() - )); - } - - let ext = match path.extension().and_then(|e| e.to_str()) { - Some("mod") => "mod", - Some("ctl") => "ctl", - _ => { - return Err(extendr_err!( - "Expected .mod or .ctl file path: {}", - path.display() - )); - } - }; - - if path.exists() { - let stem = path - .file_stem() - .ok_or_extendr_err("Could not determine model file stem")? - .to_string_lossy() - .to_string(); - if let Some(parent) = path.parent() - && parent - .file_name() - .and_then(|name| name.to_str()) - .is_some_and(|name| name == stem.as_str()) - { - let candidate = parent.with_extension(ext); - return Err(extendr_err!( - "Expected input model file, got output model file: {}\n\ - Try: {}", - path.display(), - candidate.display() - )); - } - - return Ok(path.to_path_buf()); - } - - Err(extendr_err!("File not found: {}", path.display())) -} - -/// Convert an absolute path to be relative to the pharos config directory. -/// Returns the original path if no config directory is found. -pub fn to_config_relative(path: impl AsRef) -> Result { - let path = path.as_ref(); - let config_dir = find_config_dir().map_to_extendr_err("Failed to find config dir")?; - - if let Some(dir) = config_dir { - let rel = make_relative_path(&dir, path); - return Ok(rel.to_string_lossy().to_string()); - } - - Ok(path.to_string_lossy().to_string()) -} - -/// Convert a config-relative path to an absolute path. -/// If the path is already absolute, returns it unchanged. -pub fn from_config_relative(source: impl AsRef) -> Result { - let source_path = source.as_ref(); - if source_path.is_absolute() { - return Ok(source_path.to_path_buf()); - } - - if let Some(dir) = find_config_dir().map_to_extendr_err("Failed to find config dir")? { - return Ok(dir.join(source_path)); - } - - Ok(source_path.to_path_buf()) -} - -/// Extract model_source attribute from Robj and resolve to absolute path. -fn extract_model_source(model: &Robj) -> Result { - let source = model - .get_attrib("model_source") - .ok_or_extendr_err("Model object is missing model_source attribute")?; - let source_str = source - .as_str() - .ok_or_extendr_err("model_source attribute must be a string")?; - from_config_relative(source_str) -} - -/// Extract a path from an Robj (string path or hyperion_nonmem_model object). -/// -/// If `validate_model` is true, validates the path is an existing .mod/.ctl file. -pub fn path_from_robj(input: &Robj, validate_model: bool) -> Result { - let path = if input.inherits("hyperion_nonmem_model") { - extract_model_source(input)? - } else if let Some(s) = input.as_str() { - PathBuf::from(s) - } else { - return Err(extendr_err!( - "Input must be a path or a hyperion_nonmem_model object" - )); - }; - - if validate_model { - validate_model_path(path) - } else { - Ok(path) - } -} - -fn make_relative_path(base: &Path, target: &Path) -> PathBuf { - let base_components: Vec> = base.components().collect(); - let target_components: Vec> = target.components().collect(); - - if base_components.first() != target_components.first() { - return target.to_path_buf(); - } - - let mut idx = 0; - let max = base_components.len().min(target_components.len()); - while idx < max && base_components[idx] == target_components[idx] { - idx += 1; - } - - let mut rel = PathBuf::new(); - for _ in idx..base_components.len() { - rel.push(".."); - } - for comp in target_components.iter().skip(idx) { - rel.push(comp.as_os_str()); - } - - rel -} - -/// Gives Some(Model) if model path is found -pub fn try_parse_model(path: &str) -> Option { - let path_buf = std::path::Path::new(path); - - // If a non-model file is provided (e.g., .grd), search from its parent dir. - let search_path = if path_buf.is_file() { - match path_buf.extension().and_then(|e| e.to_str()) { - Some("mod") | Some("ctl") => path_buf, - _ => path_buf.parent().unwrap_or(path_buf), - } - } else { - path_buf - }; - - let model_path = find_output_file(search_path, "mod") - .or_else(|_| find_output_file(path, "ctl")) - .ok()?; - let content = fs::read_to_string(model_path).ok()?; - let (model, _diagonstics) = Model::parse(&content).ok()?; - Some(model) -} - -/// Gets the comment type from pharos.toml configuration -/// -/// @return Option from pharos config, None if not found or config doesn't exist -pub fn get_comment_type() -> Option { - find_config_dir() - .ok() - .flatten() - .map(|dir| dir.join(CONFIG_FILENAME)) - .and_then(|path| Config::load(path).ok()) - .and_then(|config| config.nonmem.as_ref().and_then(|n| n.comments.r#type)) -} - -pub fn load_nonmem_config(run_nonmem_version: Option<&str>) -> Result<(PathBuf, NonmemConfig)> { - let p = if let Some(root_dir) = - find_config_dir().map_to_extendr_err("Failed to find config dir")? - { - root_dir.join(CONFIG_FILENAME) - } else { - std::env::current_dir() - .map_to_extendr_err("Failed to get current directory")? - .join(CONFIG_FILENAME) - }; - - if !p.exists() { - return Err(extendr_err!( - "pharos config file not found in current or parent directories", - )); - } - - let config = Config::load(&p).map_to_extendr_err("Failed to load config")?; - - let nonmem_config = config - .nonmem - .ok_or_extendr_err("pharos config file does not contain nonmem configuration")?; - - if let Some(version) = run_nonmem_version - && !nonmem_config.versions.contains_key(version) - { - return Err(extendr_err!( - "nonmem version {version} not found in config file" - )); - } - - Ok((p, nonmem_config)) -} - -#[cfg(test)] -mod tests { - use super::*; - use insta::glob; - use std::fs; - use tempfile::TempDir; - - #[test] - fn test_find_output_file_directory_input() { - let temp_dir = TempDir::new().unwrap(); - let run_dir = temp_dir.path().join("run001"); - fs::create_dir(&run_dir).unwrap(); - - let ext_file = run_dir.join("run001.ext"); - fs::write(&ext_file, "test content").unwrap(); - - let result = find_output_file(&run_dir, "ext").unwrap(); - assert_eq!(result, ext_file); - } - - #[test] - fn test_find_output_file_mod_input() { - let temp_dir = TempDir::new().unwrap(); - let mod_file = temp_dir.path().join("run001.mod"); - fs::write(&mod_file, "test content").unwrap(); - - let run_dir = temp_dir.path().join("run001"); - fs::create_dir(&run_dir).unwrap(); - - let ext_file = run_dir.join("run001.ext"); - fs::write(&ext_file, "test content").unwrap(); - - let result = find_output_file(&mod_file, "ext").unwrap(); - assert_eq!(result, ext_file); - } - - #[test] - fn test_find_output_file_already_correct() { - let temp_dir = TempDir::new().unwrap(); - let ext_file = temp_dir.path().join("run001.ext"); - fs::write(&ext_file, "test content").unwrap(); - - let result = find_output_file(&ext_file, "ext").unwrap(); - assert_eq!(result, ext_file); - } - - #[test] - fn test_find_output_file_metadata_input() { - let temp_dir = TempDir::new().unwrap(); - let metadata_file = temp_dir.path().join("run001_metadata.json"); - fs::write(&metadata_file, "{}").unwrap(); - - let run_dir = temp_dir.path().join("run001"); - fs::create_dir(&run_dir).unwrap(); - - let ext_file = run_dir.join("run001.ext"); - fs::write(&ext_file, "test content").unwrap(); - - let result = find_output_file(&metadata_file, "ext").unwrap(); - assert_eq!(result, ext_file); - } - - #[test] - fn test_find_output_file_not_found() { - let temp_dir = TempDir::new().unwrap(); - let run_dir = temp_dir.path().join("run001"); - - let result = find_output_file(&run_dir, "ext"); - assert!(result.is_err()); - } - - #[test] - fn test_try_parse_model_success() { - // Use real test data instead of creating temporary files - let test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test_data"); - glob!(test_dir, "**/*.mod", |path| { - let result = try_parse_model(path.to_str().unwrap()); - assert!( - result.is_some(), - "Expected Some(Model) when valid mod file exists in test data" - ); - }) - } - - #[test] - fn test_try_parse_model_success_for_output_file() { - let test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test_data"); - glob!(test_dir, "**/*.grd", |path| { - // Skip directories where file stem doesn't match directory name - if path.to_string_lossy().contains("run001-running") { - return; - } - let result = try_parse_model(path.to_str().unwrap()); - assert!( - result.is_some(), - "Expected Some(Model) when valid mod file exists in test data" - ); - }) - } - - #[test] - fn test_try_parse_model_no_mod_file() { - let temp_dir = TempDir::new().unwrap(); - let run_dir = temp_dir.path().join("run001"); - fs::create_dir(&run_dir).unwrap(); - - // Don't create a mod file - should return None - let result = try_parse_model(run_dir.to_str().unwrap()); - assert!( - result.is_none(), - "Expected None when mod file doesn't exist" - ); - } - - #[test] - fn test_validate_model_path_ok() { - let temp_dir = TempDir::new().unwrap(); - let mod_file = temp_dir.path().join("run001.mod"); - fs::write(&mod_file, "test content").unwrap(); - - let result = validate_model_path(&mod_file).unwrap(); - assert_eq!(result, mod_file); - } - - #[test] - fn test_validate_model_path_rejects_output_model() { - let temp_dir = TempDir::new().unwrap(); - let run_dir = temp_dir.path().join("run001"); - fs::create_dir(&run_dir).unwrap(); - let output_mod = run_dir.join("run001.mod"); - fs::write(&output_mod, "test content").unwrap(); - - let err = validate_model_path(&output_mod).unwrap_err(); - let message = format!("{err}"); - assert!(message.contains("Expected input model file")); - assert!(message.contains("Try:")); - } - - #[test] - fn test_validate_model_path_rejects_wrong_extension() { - let temp_dir = TempDir::new().unwrap(); - let txt_file = temp_dir.path().join("run001.txt"); - fs::write(&txt_file, "test content").unwrap(); - - let err = validate_model_path(&txt_file).unwrap_err(); - let message = format!("{err}"); - assert!(message.contains("Expected .mod or .ctl")); - } - - #[test] - fn test_from_config_relative_absolute() { - let temp_dir = TempDir::new().unwrap(); - let mod_file = temp_dir.path().join("run001.mod"); - fs::write(&mod_file, "test content").unwrap(); - - let result = from_config_relative(mod_file.to_string_lossy().as_ref()).unwrap(); - assert_eq!(result, mod_file); - } -} diff --git a/src/rust/nonmem/Cargo.toml b/src/rust/nonmem/Cargo.toml index 4613af79..b3eca921 100644 --- a/src/rust/nonmem/Cargo.toml +++ b/src/rust/nonmem/Cargo.toml @@ -7,6 +7,7 @@ edition = "2024" # R Integration (needed for extendr macros in modules) extendr-api = { workspace = true } hyperion-core = { path = "../core" } +hyperion-nmparser = { path = "../nmparser" } # Pharos components nonmem = { workspace = true } diff --git a/src/rust/nonmem/src/model/check.rs b/src/rust/nonmem/src/model/check.rs index 7bcd6a98..ab851e06 100644 --- a/src/rust/nonmem/src/model/check.rs +++ b/src/rust/nonmem/src/model/check.rs @@ -1,11 +1,13 @@ use extendr_api::Result; use extendr_api::prelude::*; -use hyperion_core::ResultExt; +use extendr_api::serializer::to_robj; +use hyperion_core::{OptionExt, ResultExt}; // pharos config and nonmem crates -use nonmem::check_model; +use hyperion_nmparser::robj_to_model; +use nonmem::{check_dataset as nonmem_check_dataset, check_model}; -use crate::utils::{load_nonmem_config, path_from_robj}; +use crate::utils::{from_config_relative, load_nonmem_config, path_from_robj}; use hyperion_core::extendr_err; /// Checks mod file for nmtran errors @@ -45,8 +47,37 @@ pub fn check_model_wrap(model_path: Robj) -> Result { Ok(res.exit_code) } +/// Checks model dataset +/// +/// @param model hyperion_nonmem_model object from `read_model` +/// +/// @return Dataset check results +/// @export +#[extendr] +pub fn check_dataset(model: Robj) -> Result { + let source = model + .get_attrib("model_source") + .ok_or_extendr_err("Model object is missing model_source attribute")? + .as_str() + .ok_or_extendr_err("model_source attribute must be a character")?; + let model_path = from_config_relative(source)?; + let model_dir = model_path + .parent() + .ok_or_extendr_err("Could not determine model directory")?; + + let model = robj_to_model(&model)?; + let dataset = nonmem_check_dataset(&model, model_dir).map_to_extendr_err("")?; + + let mut robj = to_robj(&dataset).map_to_extendr_err("Failed to serialize to Robj")?; + robj.set_class(["hyperion_nonmem_dataset"]) + .map_to_extendr_err("Failed to set class")?; + + Ok(robj) +} + extendr_module! { mod check; fn check_model_wrap; + fn check_dataset; } diff --git a/src/rust/nonmem/src/model/mod.rs b/src/rust/nonmem/src/model/mod.rs index 113bd691..0855a9d2 100644 --- a/src/rust/nonmem/src/model/mod.rs +++ b/src/rust/nonmem/src/model/mod.rs @@ -1,21 +1,12 @@ use extendr_api::Result; -use extendr_api::deserializer::from_robj; use extendr_api::prelude::*; -use extendr_api::serializer::to_robj; - -use fs_err as fs; -use std::path::Path; - -//pharos nonmem crate -use nonmem::Model; use nonmem::output_files::lst; +use std::path::Path; use crate::model::run_status::determine_run_status; -use crate::utils::{ - find_output_file, from_config_relative, get_comment_type, to_config_relative, - validate_model_path, -}; -use hyperion_core::{OptionExt, ResultExt}; +use crate::utils::find_output_file; +use hyperion_core::ResultExt; +use hyperion_nmparser::model_to_robj; pub mod check; pub mod copy; @@ -25,44 +16,6 @@ pub mod parameters; pub mod run_status; pub mod summary; -/// Helper to convert Model to Robj for read_model and read_model_from_lst -/// -/// This handles comment parsing and Model -> Robj + S3 setting -fn model_to_robj(model: &mut Model, path: impl AsRef) -> Result { - let path = path.as_ref(); - - let comment_type = get_comment_type(); - if let Some(c) = comment_type { - model.parse_comments(c); - }; - - let mut model_robj = to_robj(&model).map_to_extendr_err("failed to create Robj from Model")?; - - add_filename_attr(&mut model_robj, path)?; - add_model_source_attr(&mut model_robj, path)?; - add_run_status_attr(&mut model_robj, path)?; - - set_model_class(&mut model_robj) -} - -fn add_filename_attr(model_robj: &mut Robj, path: &Path) -> Result<()> { - if let Some(n) = path.file_stem().and_then(|name| name.to_str()) { - model_robj - .set_attrib("filename", n.into_robj()) - .map_to_extendr_err("Failed to set filename attribute")?; - } - Ok(()) -} - -fn add_model_source_attr(model_robj: &mut Robj, path: &Path) -> Result<()> { - let source_path = to_config_relative(path)?; - model_robj - .set_attrib("model_source", source_path.into_robj()) - .map_to_extendr_err("Failed to set model source attribute")?; - - Ok(()) -} - fn add_run_status_attr(model_robj: &mut Robj, path: &Path) -> Result<()> { if let Some(ext) = path.extension().and_then(|e| e.to_str()) && (ext == "mod" || ext == "ctl" || ext == "lst") @@ -75,40 +28,6 @@ fn add_run_status_attr(model_robj: &mut Robj, path: &Path) -> Result<()> { Ok(()) } - -fn set_model_class(model_robj: &mut Robj) -> Result { - let result = model_robj - .set_class(["hyperion_nonmem_model"]) - .map_to_extendr_err("Failed to set class")?; - - Ok(result.to_owned()) -} - -/// Helper function to reconstruct a pharos Model from hyperion_nonmem_model Robj -pub fn robj_to_model(model: &Robj) -> Result { - from_robj(model).map_to_extendr_err("Failed to create Model from Robj") -} - -/// Gets model object -/// -/// @param path path to mod or ctl file. -/// -/// @return hyperion_nonmem_model S3 object with `model_source` and `run_status` attributes -/// @export -/// -/// @examples \dontrun{ -/// read_model("model/nonmem/run001.mod") -/// } -#[extendr] -pub fn read_model(path: &str) -> Result { - let mod_path = validate_model_path(path)?; - let content = fs::read_to_string(&mod_path).map_to_extendr_err("")?; - - let mut model = Model::parse(&content).map_to_extendr_err("Failed to read model file")?; - let robj_model = model_to_robj(&mut model, mod_path)?; - Ok(robj_model) -} - /// Gets model object from lst file (internal) /// /// @param path path to lst file, model output directory, or metadata.json file. @@ -118,47 +37,14 @@ pub fn read_model(path: &str) -> Result { #[extendr] pub fn read_model_from_lst(path: &str) -> Result { let path = find_output_file(path, "lst")?; - let mut model = + let model = lst::extract_model(&path).map_to_extendr_err("Failed to extract Model from lst file")?; - let robj_model = model_to_robj(&mut model, path)?; + let mut robj_model = model_to_robj(&model, &path)?; + add_run_status_attr(&mut robj_model, &path)?; Ok(robj_model) } -/// Checks model dataset -/// -/// @param model hyperion_nonmem_model object from `read_model` -/// -/// @return Dataset check results -/// @export -/// -/// @examples \dontrun{ -/// model <- read_model("model/nonmem/run001.mod") -/// model |> check_dataset() -/// } -#[extendr] -pub fn check_dataset(model: Robj) -> Result { - let source = model - .get_attrib("model_source") - .ok_or_extendr_err("Model object is missing model_source attribute")? - .as_str() - .ok_or_extendr_err("model_source attribute must be a character")?; - let model_path = from_config_relative(source)?; - let model_dir = model_path - .parent() - .ok_or_extendr_err("Could not determine model directory")?; - - let model = robj_to_model(&model)?; - let dataset = model.check_dataset(model_dir).map_to_extendr_err("")?; - - let mut robj = to_robj(&dataset).map_to_extendr_err("Failed to serialize to Robj")?; - - robj.set_class(["hyperion_nonmem_dataset"]) - .map_to_extendr_err("Failed to set class")?; - - Ok(robj) -} - extendr_module! { mod model; use copy; @@ -169,7 +55,5 @@ extendr_module! { use metadata; use run_status; - fn read_model; - fn check_dataset; fn read_model_from_lst; } diff --git a/src/rust/nonmem/src/model/parameters.rs b/src/rust/nonmem/src/model/parameters.rs index 34ffdbcb..f159db9f 100644 --- a/src/rust/nonmem/src/model/parameters.rs +++ b/src/rust/nonmem/src/model/parameters.rs @@ -2,6 +2,7 @@ use extendr_api::Result; use extendr_api::prelude::*; use fs_err as fs; +use hyperion_nmparser::robj_to_model; use std::cmp::Ordering; use std::ffi::OsStr; use std::path::PathBuf; @@ -13,7 +14,6 @@ use nonmem::{ }; use crate::{ - model::robj_to_model, output_files::ext::create_ext_reader, output_files::{OMEGA, ParameterRow, ParameterRowBuilder, SIGMA, THETA, build_parameters_df}, utils::{find_output_file, get_comment_type, path_from_robj}, @@ -138,7 +138,7 @@ pub fn get_parameters( find_output_file(&search_path, "mod").or_else(|_| find_output_file(&search_path, "ctl"))?; let content = fs::read_to_string(&model_path).map_to_extendr_err("")?; - let mut model = Model::parse(&content).map_to_extendr_err("Failed to read model file")?; + let model = Model::parse(&content).map_to_extendr_err("Failed to read model file")?; let comment_type = get_comment_type(); let parameter_names = model @@ -224,7 +224,7 @@ pub fn get_parameters( /// @keywords internal #[extendr] pub fn get_model_parameter_names(model: Robj) -> Result { - let mut model = robj_to_model(&model)?; + let model = robj_to_model(&model)?; let comment_type = get_comment_type(); let parameter_names = model diff --git a/src/rust/nonmem/src/output_files/grd.rs b/src/rust/nonmem/src/output_files/grd.rs index 9d3b0db3..35d47cee 100644 --- a/src/rust/nonmem/src/output_files/grd.rs +++ b/src/rust/nonmem/src/output_files/grd.rs @@ -58,13 +58,13 @@ pub fn get_gradients( let search_path = path_from_robj(&path, false)?; let grd_path = find_output_file(&search_path, "grd")?; - let mut model = try_parse_model(search_path.to_string_lossy().as_ref()); + let model = try_parse_model(search_path.to_string_lossy().as_ref()); // Load config and extract comment type let comment_type = get_comment_type(); let tables = grd_reader - .parse_file(grd_path, model.as_mut(), comment_type) + .parse_file(grd_path, model.as_ref(), comment_type) .map_to_extendr_err("")?; if tables.is_empty() { From 97b546599767c525bf57203c45c68790cac5137b Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Thu, 2 Apr 2026 10:26:14 -0400 Subject: [PATCH 05/41] update documentation --- NAMESPACE | 1 - R/extendr-wrappers.R | 39 +++++++++++---------------------------- man/check_dataset.Rd | 6 ------ man/read_model.Rd | 2 +- man/read_nmmodel.Rd | 22 ---------------------- 5 files changed, 12 insertions(+), 58 deletions(-) delete mode 100644 man/read_nmmodel.Rd diff --git a/NAMESPACE b/NAMESPACE index 1f9c7dd8..cb6c4438 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -67,7 +67,6 @@ export(init) export(list_lookup_parameters) export(read_ext_file) export(read_model) -export(read_nmmodel) export(remove_parameter_from_lookup) export(set_metadata_file) export(submit_model_to_sge) diff --git a/R/extendr-wrappers.R b/R/extendr-wrappers.R index 0e68ff6a..ef687333 100644 --- a/R/extendr-wrappers.R +++ b/R/extendr-wrappers.R @@ -22,31 +22,6 @@ set_panic_message <- function() .Call(wrap__set_panic_message) find_pharos_config_file <- function() .Call(wrap__find_pharos_config_file) -#' Gets model object -#' -#' @param path path to mod or ctl file. -#' -#' @return hyperion_nonmem_model S3 object with `model_source` and `run_status` attributes -#' @export -#' -#' @examples \dontrun{ -#' read_model("model/nonmem/run001.mod") -#' } -read_model <- function(path) .Call(wrap__read_model, path) - -#' Checks model dataset -#' -#' @param model hyperion_nonmem_model object from `read_model` -#' -#' @return Dataset check results -#' @export -#' -#' @examples \dontrun{ -#' model <- read_model("model/nonmem/run001.mod") -#' model |> check_dataset() -#' } -check_dataset <- function(model) .Call(wrap__check_dataset, model) - #' Gets model object from lst file (internal) #' #' @param path path to lst file, model output directory, or metadata.json file. @@ -120,6 +95,14 @@ get_run_info <- function(path) .Call(wrap__get_run_info, path) #' } check_model <- function(model_path) .Call(wrap__check_model_wrap, model_path) +#' Checks model dataset +#' +#' @param model hyperion_nonmem_model object from `read_model` +#' +#' @return Dataset check results +#' @export +check_dataset <- function(model) .Call(wrap__check_dataset, model) + #' Get's model lineage #' #' @param model_dir path to directory containing all models, or a hyperion_nonmem_model object @@ -472,13 +455,13 @@ to_config_relative <- function(path) .Call(wrap__to_config_relative_wrap, path) #' #' @param path path to mod or ctl file. #' -#' @return hyperion_nonmem_model S3 object with `model_source` and `run_status` attributes +#' @return hyperion_nonmem_model S3 object with `model_source` attribute #' @export #' #' @examples \dontrun{ -#' read_nmmodel("model/nonmem/run001.mod") +#' read_model("model/nonmem/run001.mod") #' } -read_nmmodel <- function(path) .Call(wrap__read_nmmodel, path) +read_model <- function(path) .Call(wrap__read_model, path) #' Submits a NONMEM model to SLURM for execution #' diff --git a/man/check_dataset.Rd b/man/check_dataset.Rd index a48b502d..01c46b18 100644 --- a/man/check_dataset.Rd +++ b/man/check_dataset.Rd @@ -15,9 +15,3 @@ Dataset check results \description{ Checks model dataset } -\examples{ -\dontrun{ -model <- read_model("model/nonmem/run001.mod") -model |> check_dataset() -} -} diff --git a/man/read_model.Rd b/man/read_model.Rd index 5708d791..85cbec4d 100644 --- a/man/read_model.Rd +++ b/man/read_model.Rd @@ -10,7 +10,7 @@ read_model(path) \item{path}{path to mod or ctl file.} } \value{ -hyperion_nonmem_model S3 object with \code{model_source} and \code{run_status} attributes +hyperion_nonmem_model S3 object with \code{model_source} attribute } \description{ Gets model object diff --git a/man/read_nmmodel.Rd b/man/read_nmmodel.Rd deleted file mode 100644 index ab016d66..00000000 --- a/man/read_nmmodel.Rd +++ /dev/null @@ -1,22 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/extendr-wrappers.R -\name{read_nmmodel} -\alias{read_nmmodel} -\title{Gets model object} -\usage{ -read_nmmodel(path) -} -\arguments{ -\item{path}{path to mod or ctl file.} -} -\value{ -hyperion_nonmem_model S3 object with \code{model_source} and \code{run_status} attributes -} -\description{ -Gets model object -} -\examples{ -\dontrun{ -read_nmmodel("model/nonmem/run001.mod") -} -} From d35945333130a2f378cb9c79f8205abdda72997a Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Tue, 7 Apr 2026 09:08:51 -0400 Subject: [PATCH 06/41] update branch and render diagnostics --- src/rust/Cargo.lock | 27 ++++++++++++------------- src/rust/Cargo.toml | 8 ++++---- src/rust/nmparser/src/lib.rs | 2 +- src/rust/nonmem/src/model/parameters.rs | 14 +++++++++++-- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 096149ea..4f3f1455 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -91,9 +91,9 @@ checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "cc" -version = "1.2.58" +version = "1.2.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" +checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" dependencies = [ "find-msvc-tools", "shlex", @@ -141,7 +141,7 @@ dependencies = [ [[package]] name = "config" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=nonmem-parser2#7d4126739e45d47609572a81c91d606b8b63bf3f" +source = "git+https://github.com/a2-ai/pharos?branch=parameterization#b9fa9592050afe88b916070dfdfdfe88a8a15bc2" dependencies = [ "anyhow", "fs-err", @@ -312,9 +312,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "find-msvc-tools" @@ -564,9 +564,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" dependencies = [ "equivalent", "hashbrown 0.16.1", @@ -722,7 +722,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "nonmem" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=nonmem-parser2#7d4126739e45d47609572a81c91d606b8b63bf3f" +source = "git+https://github.com/a2-ai/pharos?branch=parameterization#b9fa9592050afe88b916070dfdfdfe88a8a15bc2" dependencies = [ "anyhow", "blake3", @@ -734,7 +734,6 @@ dependencies = [ "log", "nonmem-parser", "num_cpus", - "rand 0.9.2", "rayon", "regex", "serde", @@ -749,7 +748,7 @@ dependencies = [ [[package]] name = "nonmem-parser" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=nonmem-parser2#7d4126739e45d47609572a81c91d606b8b63bf3f" +source = "git+https://github.com/a2-ai/pharos?branch=parameterization#b9fa9592050afe88b916070dfdfdfe88a8a15bc2" dependencies = [ "anyhow", "distrs", @@ -1101,7 +1100,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=nonmem-parser2#7d4126739e45d47609572a81c91d606b8b63bf3f" +source = "git+https://github.com/a2-ai/pharos?branch=parameterization#b9fa9592050afe88b916070dfdfdfe88a8a15bc2" dependencies = [ "anyhow", "config", @@ -1114,9 +1113,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" @@ -1347,7 +1346,7 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utils" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=nonmem-parser2#7d4126739e45d47609572a81c91d606b8b63bf3f" +source = "git+https://github.com/a2-ai/pharos?branch=parameterization#b9fa9592050afe88b916070dfdfdfe88a8a15bc2" dependencies = [ "anyhow", "fs-err", diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index e6f3aa34..629f9642 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -39,10 +39,10 @@ serde = { workspace = true } extendr-api = { git = "https://github.com/extendr/extendr", branch = "main", features = ["serde"] } # Pharos components -nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "nonmem-parser2" } -nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", branch = "nonmem-parser2" } -config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "nonmem-parser2" } -scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "nonmem-parser2" } +nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "parameterization" } +nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", branch = "parameterization" } +config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "parameterization" } +scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "parameterization" } # Core utilities anyhow = "1.0.100" diff --git a/src/rust/nmparser/src/lib.rs b/src/rust/nmparser/src/lib.rs index 955694e7..a65dc1ea 100644 --- a/src/rust/nmparser/src/lib.rs +++ b/src/rust/nmparser/src/lib.rs @@ -160,7 +160,7 @@ pub fn read_model(path: &str) -> Result { Err(diags) => { let msg = diags .iter() - .map(|d| d.to_string()) + .map(|d| d.render(mod_path.as_path(), &content)) .collect::>() .join("\n"); return Err(extendr_err!("Failed to read model file:\n{msg}")); diff --git a/src/rust/nonmem/src/model/parameters.rs b/src/rust/nonmem/src/model/parameters.rs index f159db9f..1653b0ce 100644 --- a/src/rust/nonmem/src/model/parameters.rs +++ b/src/rust/nonmem/src/model/parameters.rs @@ -18,7 +18,7 @@ use crate::{ output_files::{OMEGA, ParameterRow, ParameterRowBuilder, SIGMA, THETA, build_parameters_df}, utils::{find_output_file, get_comment_type, path_from_robj}, }; -use hyperion_core::ResultExt; +use hyperion_core::{ResultExt, extendr_err}; /// Extract numeric indices from a parameter name for sorting. /// @@ -138,7 +138,17 @@ pub fn get_parameters( find_output_file(&search_path, "mod").or_else(|_| find_output_file(&search_path, "ctl"))?; let content = fs::read_to_string(&model_path).map_to_extendr_err("")?; - let model = Model::parse(&content).map_to_extendr_err("Failed to read model file")?; + let model = match Model::parse(&content) { + Ok(model) => model, + Err(diags) => { + let msg = diags + .iter() + .map(|d| d.render(model_path.as_path(), &content)) + .collect::>() + .join("\n"); + return Err(extendr_err!("Failed to read model file:\n{msg}")); + } + }; let comment_type = get_comment_type(); let parameter_names = model From 1bccf40c9bcaaf8946b2ee098e96d1a5902d45cc Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Wed, 8 Apr 2026 09:47:23 -0400 Subject: [PATCH 07/41] move to new pharos branch --- src/rust/Cargo.lock | 14 +++++++------- src/rust/Cargo.toml | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 4f3f1455..d5b1307b 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -141,7 +141,7 @@ dependencies = [ [[package]] name = "config" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=parameterization#b9fa9592050afe88b916070dfdfdfe88a8a15bc2" +source = "git+https://github.com/a2-ai/pharos?branch=raneff-parameterization#34d368a0ee583958b77f26f1251c526c76c2b76c" dependencies = [ "anyhow", "fs-err", @@ -249,9 +249,9 @@ dependencies = [ [[package]] name = "distrs" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3b3318a6ce94bae6f71c71dfbb5c91059ea2afa3c2ac86d8fb9b1f6ea5de83" +checksum = "2906d3628a885aa49a23b88e71f63e51ae8df41cd76652287f0a6179a11833a9" [[package]] name = "either" @@ -722,7 +722,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "nonmem" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=parameterization#b9fa9592050afe88b916070dfdfdfe88a8a15bc2" +source = "git+https://github.com/a2-ai/pharos?branch=raneff-parameterization#34d368a0ee583958b77f26f1251c526c76c2b76c" dependencies = [ "anyhow", "blake3", @@ -748,7 +748,7 @@ dependencies = [ [[package]] name = "nonmem-parser" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=parameterization#b9fa9592050afe88b916070dfdfdfe88a8a15bc2" +source = "git+https://github.com/a2-ai/pharos?branch=raneff-parameterization#34d368a0ee583958b77f26f1251c526c76c2b76c" dependencies = [ "anyhow", "distrs", @@ -1100,7 +1100,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=parameterization#b9fa9592050afe88b916070dfdfdfe88a8a15bc2" +source = "git+https://github.com/a2-ai/pharos?branch=raneff-parameterization#34d368a0ee583958b77f26f1251c526c76c2b76c" dependencies = [ "anyhow", "config", @@ -1346,7 +1346,7 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utils" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=parameterization#b9fa9592050afe88b916070dfdfdfe88a8a15bc2" +source = "git+https://github.com/a2-ai/pharos?branch=raneff-parameterization#34d368a0ee583958b77f26f1251c526c76c2b76c" dependencies = [ "anyhow", "fs-err", diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 629f9642..24282a8a 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -39,10 +39,10 @@ serde = { workspace = true } extendr-api = { git = "https://github.com/extendr/extendr", branch = "main", features = ["serde"] } # Pharos components -nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "parameterization" } -nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", branch = "parameterization" } -config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "parameterization" } -scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "parameterization" } +nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "raneff-parameterization" } +nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", branch = "raneff-parameterization" } +config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "raneff-parameterization" } +scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "raneff-parameterization" } # Core utilities anyhow = "1.0.100" From fc054ad2b92f0c4dbb0bc75c8095a0ebaabf48d2 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Wed, 8 Apr 2026 10:27:33 -0400 Subject: [PATCH 08/41] update pharos dep --- src/rust/Cargo.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index d5b1307b..75f2a101 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -141,7 +141,7 @@ dependencies = [ [[package]] name = "config" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=raneff-parameterization#34d368a0ee583958b77f26f1251c526c76c2b76c" +source = "git+https://github.com/a2-ai/pharos?branch=raneff-parameterization#2c1827755b526b7ce36232c98889136fe7ecd263" dependencies = [ "anyhow", "fs-err", @@ -722,7 +722,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "nonmem" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=raneff-parameterization#34d368a0ee583958b77f26f1251c526c76c2b76c" +source = "git+https://github.com/a2-ai/pharos?branch=raneff-parameterization#2c1827755b526b7ce36232c98889136fe7ecd263" dependencies = [ "anyhow", "blake3", @@ -748,7 +748,7 @@ dependencies = [ [[package]] name = "nonmem-parser" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=raneff-parameterization#34d368a0ee583958b77f26f1251c526c76c2b76c" +source = "git+https://github.com/a2-ai/pharos?branch=raneff-parameterization#2c1827755b526b7ce36232c98889136fe7ecd263" dependencies = [ "anyhow", "distrs", @@ -1100,7 +1100,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=raneff-parameterization#34d368a0ee583958b77f26f1251c526c76c2b76c" +source = "git+https://github.com/a2-ai/pharos?branch=raneff-parameterization#2c1827755b526b7ce36232c98889136fe7ecd263" dependencies = [ "anyhow", "config", @@ -1346,7 +1346,7 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utils" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=raneff-parameterization#34d368a0ee583958b77f26f1251c526c76c2b76c" +source = "git+https://github.com/a2-ai/pharos?branch=raneff-parameterization#2c1827755b526b7ce36232c98889136fe7ecd263" dependencies = [ "anyhow", "fs-err", From 43f5520b8739859b66829ae23136d5887535ba9f Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Fri, 10 Apr 2026 11:35:54 -0400 Subject: [PATCH 09/41] update pharos/extendr remove panic hook --- R/comments-parsing.R | 22 +- R/model-methods.R | 169 +++--- R/zzz.R | 2 +- inst/extdata/mod/everything.mod | 81 ++- src/rust/Cargo.lock | 31 +- src/rust/Cargo.toml | 8 +- src/rust/nmparser/Cargo.toml | 1 + src/rust/nmparser/src/lib.rs | 21 +- src/rust/nonmem/src/model/mod.rs | 4 +- .../hyperion-model-knit/model-knit-1001.html | 3 +- .../model-knit-everything.html | 481 ++++++++++++++++-- .../model-knit-example1.html | 3 +- .../model-knit-iiv-cov.html | 3 +- .../hyperion-model-knit/model-knit-iov.html | 1 - .../model-knit-multiline_table.html | 3 +- .../model-knit-nmexample.html | 3 +- tests/testthat/_snaps/hyperion-model-print.md | 114 ++++- tests/testthat/test-model-methods-edge.R | 23 +- 18 files changed, 743 insertions(+), 230 deletions(-) diff --git a/R/comments-parsing.R b/R/comments-parsing.R index 7a3d088d..b56fc541 100644 --- a/R/comments-parsing.R +++ b/R/comments-parsing.R @@ -260,10 +260,10 @@ extract_comments <- function(mod) { parsed <- list() raw <- list() - for (i in seq_along(mod$theta_parameters)) { + for (i in seq_along(mod$thetas)) { old_name <- paste0("THETA", i) - parsed[[old_name]] <- mod$theta_parameters[[i]]$parsed_comment - raw[[old_name]] <- mod$theta_parameters[[i]]$comment + parsed[[old_name]] <- mod$thetas[[i]]$parsed_comment + raw[[old_name]] <- mod$thetas[[i]]$comment } result <- extract_block_comments(parsed, raw, mod$omega_blocks, "OMEGA") @@ -275,6 +275,16 @@ extract_comments <- function(mod) { list(parsed = result$parsed, raw = result$raw) } +#' Unwrap ParsedRaneffComment enum (Omega/Sigma wrapper) to get the inner +#' parsed comment that the type1/type2 parsing functions expect. +#' @noRd +unwrap_raneff_comment <- function(parsed_comment) { + if (is.null(parsed_comment)) { + return(NULL) + } + parsed_comment$Omega %||% parsed_comment$Sigma %||% parsed_comment +} + #' @noRd extract_block_comments <- function(parsed, raw, blocks, prefix) { row <- 1 @@ -291,7 +301,7 @@ extract_block_comments <- function(parsed, raw, blocks, prefix) { if (is_diagonal) { for (param in block$parameters) { old_name <- sprintf("%s(%d,%d)", prefix, row, row) - parsed[[old_name]] <- param$parsed_comment + parsed[[old_name]] <- unwrap_raneff_comment(param$parsed_comment) raw[[old_name]] <- param$comment row <- row + 1 } @@ -312,7 +322,9 @@ extract_block_comments <- function(parsed, raw, blocks, prefix) { start_row + j - 1 ) row_names[j] <- old_name - parsed[[old_name]] <- block$parameters[[param_idx]]$parsed_comment + parsed[[old_name]] <- unwrap_raneff_comment( + block$parameters[[param_idx]]$parsed_comment + ) raw[[old_name]] <- block$parameters[[param_idx]]$comment param_idx <- param_idx + 1 } diff --git a/R/model-methods.R b/R/model-methods.R index 1e681f64..5af2e544 100644 --- a/R/model-methods.R +++ b/R/model-methods.R @@ -18,18 +18,6 @@ print.hyperion_nonmem_model <- function(x, digits = NULL, ...) { cli::cli_text("{.strong Run Status:} {parts$run_status}") } - if (!is.null(parts$records)) { - cli::cli_text("{.strong Records:} {parts$records$count} record blocks") - if (length(parts$records$types) > 0) { - cli::cli_text("{.strong Record Types:}") - for (i in seq_along(parts$records$types)) { - type <- names(parts$records$types)[i] - count <- parts$records$types[i] - cli::cli_text(" \u2022 {type}: {count}") - } - } - } - if (!is.null(parts$data$dataset)) { cli::cli_text("{.strong Dataset:} {parts$data$dataset}") } @@ -240,8 +228,8 @@ build_not_run_summary <- function(object) { if (!is.null(object$problem)) { if (is.character(object$problem) && length(object$problem) > 0) { problem <- object$problem - } else if (is.list(object$problem) && !is.null(object$problem$title)) { - problem <- object$problem$title + } else if (is.list(object$problem) && !is.null(object$problem$text)) { + problem <- object$problem$text } } @@ -300,8 +288,7 @@ validate_n_iterations <- function(n_iterations) { #' @rawNamespace S3method(utils::str, hyperion_nonmem_model) str.hyperion_nonmem_model <- function(object, ...) { class(object) <- "list" - object$tokens <- NULL - object$token_ranges <- NULL + object$source <- NULL utils::str(object, ...) } @@ -314,7 +301,7 @@ str.hyperion_nonmem_model <- function(object, ...) { #' @return The element value, or NULL for restricted fields #' @rawNamespace S3method(base::`$`, hyperion_nonmem_model) `$.hyperion_nonmem_model` <- function(x, name) { - if (name %in% c("tokens", "token_ranges")) { + if (name %in% c("source")) { return(NULL) } .subset2(x, name) @@ -322,7 +309,7 @@ str.hyperion_nonmem_model <- function(object, ...) { #' @rawNamespace S3method(base::`[[`, hyperion_nonmem_model) `[[.hyperion_nonmem_model` <- function(x, i, ...) { - if (is.character(i) && i %in% c("tokens", "token_ranges")) { + if (is.character(i) && i %in% c("source")) { return(NULL) } NextMethod("[[") @@ -331,7 +318,7 @@ str.hyperion_nonmem_model <- function(object, ...) { #' @rawNamespace S3method(base::names, hyperion_nonmem_model) names.hyperion_nonmem_model <- function(x) { n <- NextMethod("names") - setdiff(n, c("tokens", "token_ranges")) + setdiff(n, c("source")) } #' @noRd @@ -347,32 +334,13 @@ build_model_display_parts <- function(x, digits = NULL) { if (!is.null(x$problem)) { if (is.character(x$problem) && length(x$problem) > 0) { problem <- x$problem - } else if (is.list(x$problem) && !is.null(x$problem$title)) { - problem <- x$problem$title + } else if (is.list(x$problem) && !is.null(x$problem$text)) { + problem <- x$problem$text } } run_status <- format_run_status(refresh_run_status(x)) - records <- NULL - if (!is.null(x$records)) { - if (length(x$records) > 0) { - record_types <- sapply(x$records, function(r) { - if (is.list(r) && !is.null(r$record_type)) { - r$record_type - } else { - NA_character_ - } - }) - } else { - record_types <- character(0) - } - records <- list( - count = length(x$records), - types = table(record_types) - ) - } - data_info <- list(dataset = NULL, ignore = NULL, num_records = NULL) if (!is.null(x$data)) { if (is.character(x$data) && length(x$data) > 0) { @@ -401,16 +369,17 @@ build_model_display_parts <- function(x, digits = NULL) { ) for (col in x$input_columns) { - if (!is.null(col$Included)) { - included_cols <- c(included_cols, col$Included) - } else if (!is.null(col$Dropped)) { - dropped_cols <- c(dropped_cols, col$Dropped) - } else if (!is.null(col$Aliased)) { + kind <- col$kind + if (!is.null(kind$Included)) { + included_cols <- c(included_cols, kind$Included) + } else if (!is.null(kind$Dropped)) { + dropped_cols <- c(dropped_cols, kind$Dropped) + } else if (!is.null(kind$Aliased)) { aliased_cols <- rbind( aliased_cols, data.frame( - from = col$Aliased$from, - to = col$Aliased$to, + from = kind$Aliased$from, + to = kind$Aliased$to, stringsAsFactors = FALSE ) ) @@ -458,7 +427,6 @@ build_model_display_parts <- function(x, digits = NULL) { title = title, problem = problem, run_status = run_status, - records = records, data = data_info, input_columns = input_columns, tables = tables @@ -488,27 +456,50 @@ format_run_status <- function(run_status) { create_blocksame_data <- function(param_names, prev_block) { # BlockSame copies everything from the previous Block's parameters # but uses new parameter names (e.g., OMEGA(8,8) instead of OMEGA(7,7)) + num_params <- length(param_names) data.frame( Parameter = param_names, Initial = sapply( prev_block$parameters, - function(p) p$initial_value %||% NA + function(p) p$value %||% NA ), - Lower = sapply(prev_block$parameters, function(p) p$lower_bound %||% NA), - Upper = sapply(prev_block$parameters, function(p) p$upper_bound %||% NA), - Fixed = sapply( - prev_block$parameters, - function(p) ifelse(p$is_fixed %||% FALSE, "Yes", "No") + Fixed = rep( + ifelse(prev_block$fixed %||% FALSE, "Yes", "No"), + num_params ), Parametrization = rep( - prev_block$parametrization %||% "", - length(param_names) + format_parametrization(prev_block$parametrization), + num_params ), Comment = sapply(prev_block$parameters, function(p) p$comment %||% ""), stringsAsFactors = FALSE ) } +#' Format a Parametrization enum value for display +#' @noRd +format_parametrization <- function(param) { + if (is.null(param)) { + return("") + } + if (identical(param, "Cholesky")) { + return("Cholesky") + } + if (is.list(param) && !is.null(param$Axes)) { + parts <- c() + diag <- param$Axes$diagonal + off_diag <- param$Axes$off_diagonal + if (!is.null(diag)) { + parts <- c(parts, diag) + } + if (!is.null(off_diag)) { + parts <- c(parts, off_diag) + } + return(paste(parts, collapse = " ")) + } + "" +} + #' Get formatted theta parameter data (shared by console and knit functions) #' @@ -519,21 +510,21 @@ create_blocksame_data <- function(param_names, prev_block) { #' @keywords internal #' @noRd get_theta_parameter_data <- function(x, digits = NULL, theta_names) { - if (is.null(x$theta_parameters) || length(x$theta_parameters) == 0) { + if (is.null(x$thetas) || length(x$thetas) == 0) { return(NULL) } # Build parameter table param_data <- data.frame( Parameter = theta_names, - Initial = sapply(x$theta_parameters, function(p) p$initial_value %||% NA), - Lower = sapply(x$theta_parameters, function(p) p$lower_bound %||% NA), - Upper = sapply(x$theta_parameters, function(p) p$upper_bound %||% NA), + Initial = sapply(x$thetas, function(p) p$init %||% NA), + Lower = sapply(x$thetas, function(p) p$lower %||% NA), + Upper = sapply(x$thetas, function(p) p$upper %||% NA), Fixed = sapply( - x$theta_parameters, - function(p) ifelse(p$is_fixed %||% FALSE, "Yes", "No") + x$thetas, + function(p) ifelse(p$fixed %||% FALSE, "Yes", "No") ), - Comment = sapply(x$theta_parameters, function(p) p$comment %||% ""), + Comment = sapply(x$thetas, function(p) p$comment %||% ""), stringsAsFactors = FALSE ) @@ -570,14 +561,15 @@ get_random_effect_parameter_data <- function( block_data <- data.frame( Parameter = param_names[param_idx:(param_idx + num_params - 1)], - Initial = sapply(block$parameters, function(p) p$initial_value %||% NA), - Lower = sapply(block$parameters, function(p) p$lower_bound %||% NA), - Upper = sapply(block$parameters, function(p) p$upper_bound %||% NA), - Fixed = sapply( - block$parameters, - function(p) ifelse(p$is_fixed %||% FALSE, "Yes", "No") + Initial = sapply(block$parameters, function(p) p$value %||% NA), + Fixed = rep( + ifelse(block$fixed %||% FALSE, "Yes", "No"), + num_params + ), + Parametrization = rep( + format_parametrization(block$parametrization), + num_params ), - Parametrization = rep(block$parametrization %||% "", num_params), Comment = sapply(block$parameters, function(p) p$comment %||% ""), stringsAsFactors = FALSE ) @@ -659,16 +651,25 @@ format_ignore_condition <- function(ignore_obj) { # Format ValueFilter as field.op.value (e.g., "AN01FL.EQ.0") field <- ignore_obj$ValueFilter$field %||% NA_character_ op <- ignore_obj$ValueFilter$op %||% NA_character_ - value <- ignore_obj$ValueFilter$value %||% NA_character_ + + # Unwrap DataValueFilterKind enum (Number(f64) or String(String)) + raw_value <- ignore_obj$ValueFilter$value + if (is.list(raw_value)) { + value <- raw_value$Number %||% raw_value$String %||% NA_character_ + } else { + value <- raw_value %||% NA_character_ + } # Convert operation names to NONMEM-style operators op_map <- c( "Equal" = "EQ", "NotEqual" = "NE", "Greater" = "GT", - "GreaterEqual" = "GE", - "Less" = "LT", - "LessEqual" = "LE" + "GreaterOrEqual" = "GE", + "Lower" = "LT", + "LowerOrEqual" = "LE", + "EqualNumeric" = "EQN", + "NotEqualNumeric" = "NEN" ) op_symbol <- op_map[op] if (is.na(op_symbol) || is.null(op_symbol)) { @@ -713,26 +714,6 @@ knit_print.hyperion_nonmem_model <- function(x, ...) { ) } - if (!is.null(parts$records)) { - output <- c( - output, - paste0( - "Records: ", - parts$records$count, - " record blocks" - ) - ) - if (length(parts$records$types) > 0) { - output <- c(output, "Record Types:") - for (i in seq_along(parts$records$types)) { - type <- names(parts$records$types)[i] - count <- parts$records$types[i] - output <- c(output, paste0("- ", type, ": ", count)) - } - } - } - output <- c(output, "") - if (!is.null(parts$data$dataset)) { output <- c( output, diff --git a/R/zzz.R b/R/zzz.R index 591216cc..8be006ea 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -1,6 +1,6 @@ .onLoad <- function(libname, pkgname) { S7::methods_register() - set_panic_message() + #set_panic_message() # Set default hyperion options if not already set if (is.null(getOption("hyperion.significant_number_display"))) { diff --git a/inst/extdata/mod/everything.mod b/inst/extdata/mod/everything.mod index a747a221..0e6a3baa 100644 --- a/inst/extdata/mod/everything.mod +++ b/inst/extdata/mod/everything.mod @@ -2,21 +2,38 @@ $PROBLEM Some header #2 $INPUT ID TIME DV DOSE=AMT DV WT AGE SEX CREA DATE=DROP $DATA ..\data.csv IGNORE=# IGNORE=(DVID.EQ.3) - IGNORE=(ID.EQ.3.14) - ACCEPT=(AGE.GT.3,SEX.EQ.1) + IGN(ID.EQ.3.14) + IGNORE=(DVID==3) + IGNORE=(AGE>=18) + IGNORE=(AGE>3,AGE<100) + IGNORE=(AGE<=65) + IGNORE=(TYPE/=0) + IGNORE=(TYPE=1) + IGNORE=(TYPE.EQN.1) + IGNORE=(TYPE.NEN.2) + IGNORE=(TYPE 1) RECORDS=200 + NULL=. LAST20=00 -$SUBROUTINES ADVAN4 TRANS4 OTHER=fa.90 +$SUBROUTINES ADVAN4 TOL=9 TRANS4 OTHER=fa.90 +$ABBR REPLACE ETA(1)=ETA(3) +$ABBR REPLACE THETA(1)=THETA(5) +$ABBREVIATED COMRES=5 DERIV2=NO $PK TVCL = THETA(1)*(WT/70)**THETA(6) CL = TVCL * EXP(ETA(1)) $THETA 1.5 (0,0.5,2) ; THETA(1) and THETA(2) +$THETA (-INF, 0.5, 10) ; THETA with -INF lower bound +$THETA (0, 5, INF) ; THETA with INF upper bound +$THETA (0, 0.1)x3 ; Three identical THETAs +$THETA CL=(0, 1.5, 10) ; Named THETA +$THETA NAMES(KA, V2, Q) (0, 0.5) (0, 10) (0, 2) ; NAMES syntax +$THETA NAMES(A, B, C) (1, 1.1)x3 ; Three identical THETAs with NAMES $THETA 2.3 FIX ; THETA(3) $THETA 0.8 0.25 ; THETA(4) and THETA(5) $THETA (1,2.3 FIX) ; THETA(6) (0.75 FIX) ; THETA(7) - $OMEGA 0.04 ; ETA(1) - CL (diagonal) $OMEGA .17 @@ -26,33 +43,55 @@ $OMEGA BLOCK(2) CORR $OMEGA BLOCK(2) SAME ; ETA(4), ETA(5) - same structure as above -$OMEGA -(0,0.1,1 FIX) ; ETA(6) - fixed diagonal +$OMEGA BLOCK(2) FIX ; ETA(7), ETA(8) - same structure as above +0.011207 +0 0.338724 + +$OMEGA BLOCK(4) +0.1 +0.01 0.1 +(0.01)x2 0.1 +(0.01)x3 0.1 $SIGMA BLOCK(2) 0.01 ; Proportional error variance 0.002 0.25 ; Prop-Add covariance, Additive error variance +$SIGMA +1 FIXED +0.0360 + +$OMEGA ECL=.4 ; Label=Value syntax for diagonal +$OMEGA BLOCK(2) +EV1= 0.3 +EQ= 0.01 0.35 ; Label=Value syntax in block + +$OMEGA BLOCK(4) NAMES(ECL2,EV2,EQ2,EV3) VALUES(0.03,0.01) ; NAMES with VALUES + +$OMEGA BLOCK(3) CORR ; flag before values +0.2 +0.3 0.15 +0.1 0.05 0.3 + +$OMEGA BLOCK(3) ; flag after values +0.2 +0.3 0.15 +0.1 0.05 0.3 CORR + +$OMEGA BLOCK(3) ; FIX interleaved among values +6. +.005 FIX .3 +.001 .002 .1 + +$SIGMA PROP=0.04 ; Label=Value syntax for SIGMA +$SIGMA 0.01 0.02 ; diagonal SIGMA + $EST METHOD=0 SLOW $EST MAXEVAL=9999 METHOD=1 INTER PRINT=5 MSFO=../2.MSF $EST MAXEVAL=9999 METHOD=1 INTER PRINT=5 FILE=run001.est $ESTIMATION MAXEVAL=9999 METHOD=IMP INTER FILE=est -$TABLE ID TIME AMT EVID IPRED AGE WT MDV ONEHEADER NOPRINT FILE=../2.TAB +$TABLE ID TIME AMT EVID IPRED AGE WT MDV ETAS(1:LAST) ONEHEADER NOPRINT FILE=../2.TAB $TABLE ID FILE=001.tab $TABLE ID TIME AMT EVID AGE WT MDV KA CL V2 V3 Q BETA HLBE ONEHEADER NOPRINT FILE=../2par.TAB $SIM ONLYSIM - - - -Parameter Initial Lower Upper Fixed Parametrization Comment ------------ -------- ------ ------ ------ ---------------- -------------------------------------------- -OMEGA(1,1) 0.04 NA NA no ETA(1) - CL (diagonal) -OMEGA(2,2) 0.17 NA NA no -OMEGA(3,3) 0.20 NA NA no Correlation ETA(2) - V (SD) -OMEGA(4,3) 0.30 NA NA no Correlation ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) -OMEGA(4,4) 0.15 NA NA no Correlation ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) -OMEGA(5,5) 0.20 NA NA no Correlation ETA(4), ETA(5) - same structure as above -OMEGA(6,5) 0.30 NA NA no Correlation ETA(4), ETA(5) - same structure as above -OMEGA(6,6) 0.15 NA NA no Correlation ETA(4), ETA(5) - same structure as above -OMEGA(7,7) 0.10 0 1 yes ETA(6) - fixed diagonal diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 75f2a101..12f3ccad 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -91,9 +91,9 @@ checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "cc" -version = "1.2.59" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", "shlex", @@ -141,7 +141,7 @@ dependencies = [ [[package]] name = "config" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=raneff-parameterization#2c1827755b526b7ce36232c98889136fe7ecd263" +source = "git+https://github.com/a2-ai/pharos?branch=pub-comment#4071df7a39ae1f6a08f4cff021287f7dfd7c1c22" dependencies = [ "anyhow", "fs-err", @@ -284,7 +284,7 @@ dependencies = [ [[package]] name = "extendr-api" version = "0.8.1" -source = "git+https://github.com/extendr/extendr?branch=main#c1651f52dd94fdd56424d73b0b1951d3b94a364b" +source = "git+https://github.com/extendr/extendr?branch=main#8aafb67b77cd112dd6192ff05efa3bade7ea8e8b" dependencies = [ "extendr-ffi", "extendr-macros", @@ -297,12 +297,12 @@ dependencies = [ [[package]] name = "extendr-ffi" version = "0.8.1" -source = "git+https://github.com/extendr/extendr?branch=main#c1651f52dd94fdd56424d73b0b1951d3b94a364b" +source = "git+https://github.com/extendr/extendr?branch=main#8aafb67b77cd112dd6192ff05efa3bade7ea8e8b" [[package]] name = "extendr-macros" version = "0.8.1" -source = "git+https://github.com/extendr/extendr?branch=main#c1651f52dd94fdd56424d73b0b1951d3b94a364b" +source = "git+https://github.com/extendr/extendr?branch=main#8aafb67b77cd112dd6192ff05efa3bade7ea8e8b" dependencies = [ "lazy_static", "proc-macro2", @@ -430,9 +430,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "heck" @@ -483,6 +483,7 @@ dependencies = [ name = "hyperion-nmparser" version = "0.1.0" dependencies = [ + "config", "extendr-api", "fs-err", "hyperion-core", @@ -564,12 +565,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] @@ -722,7 +723,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "nonmem" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=raneff-parameterization#2c1827755b526b7ce36232c98889136fe7ecd263" +source = "git+https://github.com/a2-ai/pharos?branch=pub-comment#4071df7a39ae1f6a08f4cff021287f7dfd7c1c22" dependencies = [ "anyhow", "blake3", @@ -748,7 +749,7 @@ dependencies = [ [[package]] name = "nonmem-parser" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=raneff-parameterization#2c1827755b526b7ce36232c98889136fe7ecd263" +source = "git+https://github.com/a2-ai/pharos?branch=pub-comment#4071df7a39ae1f6a08f4cff021287f7dfd7c1c22" dependencies = [ "anyhow", "distrs", @@ -1100,7 +1101,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=raneff-parameterization#2c1827755b526b7ce36232c98889136fe7ecd263" +source = "git+https://github.com/a2-ai/pharos?branch=pub-comment#4071df7a39ae1f6a08f4cff021287f7dfd7c1c22" dependencies = [ "anyhow", "config", @@ -1346,7 +1347,7 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utils" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=raneff-parameterization#2c1827755b526b7ce36232c98889136fe7ecd263" +source = "git+https://github.com/a2-ai/pharos?branch=pub-comment#4071df7a39ae1f6a08f4cff021287f7dfd7c1c22" dependencies = [ "anyhow", "fs-err", diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 24282a8a..9740fb1a 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -39,10 +39,10 @@ serde = { workspace = true } extendr-api = { git = "https://github.com/extendr/extendr", branch = "main", features = ["serde"] } # Pharos components -nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "raneff-parameterization" } -nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", branch = "raneff-parameterization" } -config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "raneff-parameterization" } -scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "raneff-parameterization" } +nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "pub-comment" } +nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", branch = "pub-comment" } +config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "pub-comment" } +scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "pub-comment" } # Core utilities anyhow = "1.0.100" diff --git a/src/rust/nmparser/Cargo.toml b/src/rust/nmparser/Cargo.toml index 90a99c04..b5d5f1e4 100644 --- a/src/rust/nmparser/Cargo.toml +++ b/src/rust/nmparser/Cargo.toml @@ -10,6 +10,7 @@ hyperion-core = { path = "../core" } # Pharos components nmparser = {workspace = true} +config = { workspace = true } # Core utilities fs-err = { workspace = true } diff --git a/src/rust/nmparser/src/lib.rs b/src/rust/nmparser/src/lib.rs index a65dc1ea..24a43be4 100644 --- a/src/rust/nmparser/src/lib.rs +++ b/src/rust/nmparser/src/lib.rs @@ -12,10 +12,25 @@ use hyperion_core::find_config_dir; use hyperion_core::{OptionExt, ResultExt, extendr_err}; use nmparser::Model; +/// Get comment_type from pharos.toml config, if configured. +fn get_comment_type() -> Option { + find_config_dir() + .ok() + .flatten() + .map(|dir| dir.join("pharos.toml")) + .and_then(|p| config::Config::load(p).ok()) + .and_then(|c| c.nonmem.as_ref().and_then(|n| n.comments.r#type)) +} + /// Helper to convert Model to Robj for read_model and other model readers. -pub fn model_to_robj(model: &Model, path: impl AsRef) -> Result { +pub fn model_to_robj(model: &mut Model, path: impl AsRef) -> Result { let path = path.as_ref(); + // Populate parsed_comment fields before serialization + if let Some(ct) = get_comment_type() { + model.populate_parsed_comments(ct); + } + let mut model_robj = to_robj(model).map_to_extendr_err("failed to create Robj from Model")?; add_filename_attr(&mut model_robj, path)?; @@ -155,7 +170,7 @@ pub fn read_model(path: &str) -> Result { let mod_path = validate_model_path(path)?; let content = fs::read_to_string(&mod_path).map_to_extendr_err("")?; - let model = match Model::parse(&content) { + let mut model = match Model::parse(&content) { Ok(model) => model, Err(diags) => { let msg = diags @@ -166,7 +181,7 @@ pub fn read_model(path: &str) -> Result { return Err(extendr_err!("Failed to read model file:\n{msg}")); } }; - let robj_model = model_to_robj(&model, mod_path)?; + let robj_model = model_to_robj(&mut model, mod_path)?; Ok(robj_model) } diff --git a/src/rust/nonmem/src/model/mod.rs b/src/rust/nonmem/src/model/mod.rs index 0855a9d2..35bf0317 100644 --- a/src/rust/nonmem/src/model/mod.rs +++ b/src/rust/nonmem/src/model/mod.rs @@ -37,9 +37,9 @@ fn add_run_status_attr(model_robj: &mut Robj, path: &Path) -> Result<()> { #[extendr] pub fn read_model_from_lst(path: &str) -> Result { let path = find_output_file(path, "lst")?; - let model = + let mut model = lst::extract_model(&path).map_to_extendr_err("Failed to extract Model from lst file")?; - let mut robj_model = model_to_robj(&model, &path)?; + let mut robj_model = model_to_robj(&mut model, &path)?; add_run_status_attr(&mut robj_model, &path)?; Ok(robj_model) diff --git a/tests/testthat/_snaps/hyperion-model-knit/model-knit-1001.html b/tests/testthat/_snaps/hyperion-model-knit/model-knit-1001.html index 960962a5..2788efe3 100644 --- a/tests/testthat/_snaps/hyperion-model-knit/model-knit-1001.html +++ b/tests/testthat/_snaps/hyperion-model-knit/model-knit-1001.html @@ -5,11 +5,12 @@ Run Status: Not Run - Dataset: ../../../../data/derived/PK_Oral_Ex1.csv Ignore: @ +Aliased Columns: ATFD → TIME, ODV → DV + Theta Parameters diff --git a/tests/testthat/_snaps/hyperion-model-knit/model-knit-everything.html b/tests/testthat/_snaps/hyperion-model-knit/model-knit-everything.html index a0733bf4..f905fd81 100644 --- a/tests/testthat/_snaps/hyperion-model-knit/model-knit-everything.html +++ b/tests/testthat/_snaps/hyperion-model-knit/model-knit-everything.html @@ -5,16 +5,15 @@ Run Status: Not Run - Dataset: ../data.csv -Ignore: #, DVID.EQ.3, ID.EQ.3.14 +Ignore: #, DVID.EQ.3, ID.EQ.3.14, DVID.EQ.3, AGE.GE.18, AGE.GT.3, AGE.LT.100, AGE.LE.65, TYPE.NE.0, TYPE.EQ.1, TYPE.EQN.1, TYPE.NEN.2, TYPE.EQ.1 Records: 200 Dropped Columns: DATE -Aliased Columns: AMT → DOSE +Aliased Columns: DOSE → AMT @@ -50,6 +49,102 @@ THETA3 + 0.5 + -Inf + 10 + No + THETA with -INF lower bound + + + THETA4 + 5 + 0 + Inf + No + THETA with INF upper bound + + + THETA5 + 0.1 + 0 + NA + No + Three identical THETAs + + + THETA6 + 0.1 + 0 + NA + No + Three identical THETAs + + + THETA7 + 0.1 + 0 + NA + No + Three identical THETAs + + + THETA8 + 1.5 + 0 + 10 + No + Named THETA + + + THETA9 + 0.5 + 0 + NA + No + NAMES syntax + + + THETA10 + 10 + 0 + NA + No + NAMES syntax + + + THETA11 + 2 + 0 + NA + No + NAMES syntax + + + THETA12 + 1.1 + 1 + NA + No + Three identical THETAs with NAMES + + + THETA13 + 1.1 + 1 + NA + No + Three identical THETAs with NAMES + + + THETA14 + 1.1 + 1 + NA + No + Three identical THETAs with NAMES + + + THETA15 2.3 NA NA @@ -57,7 +152,7 @@ THETA(3) - THETA4 + THETA16 0.8 NA NA @@ -65,7 +160,7 @@ THETA(4) and THETA(5) - THETA5 + THETA17 0.25 NA NA @@ -73,7 +168,7 @@ THETA(4) and THETA(5) - THETA6 + THETA18 2.3 1 NA @@ -81,7 +176,7 @@ THETA(6) - THETA7 + THETA19 0.75 NA NA @@ -100,8 +195,6 @@ Parameter Initial - Lower - Upper Fixed Parametrization Comment @@ -111,8 +204,6 @@ OMEGA(1,1) 0.04 - NA - NA No ETA(1) - CL (diagonal) @@ -120,8 +211,6 @@ OMEGA(2,2) 0.17 - NA - NA No @@ -129,65 +218,359 @@ OMEGA(3,3) 0.2 - NA - NA No - Correlation + Corr ETA(2) - V (SD) OMEGA(4,3) 0.3 - NA - NA No - Correlation + Corr ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) OMEGA(4,4) 0.15 - NA - NA No - Correlation + Corr ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) OMEGA(5,5) 0.2 - NA - NA No - Correlation + Corr ETA(2) - V (SD) OMEGA(6,5) 0.3 - NA - NA No - Correlation + Corr ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) OMEGA(6,6) 0.15 - NA - NA No - Correlation + Corr ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) OMEGA(7,7) - 0.1 + 0.01121 + Yes + + + + + OMEGA(8,7) 0 - 1 Yes - ETA(6) - fixed diagonal + + + + OMEGA(8,8) + 0.3387 + Yes + + + + + OMEGA(9,9) + 0.1 + No + + + + + OMEGA(10,9) + 0.01 + No + + + + + OMEGA(10,10) + 0.1 + No + + + + + OMEGA(11,9) + 0.01 + No + + + + + OMEGA(11,10) + 0.01 + No + + + + + OMEGA(11,11) + 0.1 + No + + + + + OMEGA(12,9) + 0.01 + No + + + + + OMEGA(12,10) + 0.01 + No + + + + + OMEGA(12,11) + 0.01 + No + + + + + OMEGA(12,12) + 0.1 + No + + + + + OMEGA(13,13) + 0.4 + No + + Label=Value syntax for diagonal + + + OMEGA(14,14) + 0.3 + No + + + + + OMEGA(15,14) + 0.01 + No + + Label=Value syntax in block + + + OMEGA(15,15) + 0.35 + No + + Label=Value syntax in block + + + OMEGA(16,16) + 0.03 + No + + + + + OMEGA(17,16) + 0.01 + No + + + + + OMEGA(17,17) + 0.03 + No + + + + + OMEGA(18,16) + 0.01 + No + + + + + OMEGA(18,17) + 0.01 + No + + + + + OMEGA(18,18) + 0.03 + No + + + + + OMEGA(19,16) + 0.01 + No + + + + + OMEGA(19,17) + 0.01 + No + + + + + OMEGA(19,18) + 0.01 + No + + + + + OMEGA(19,19) + 0.03 + No + + + + + OMEGA(20,20) + 0.2 + No + Corr + + + + OMEGA(21,20) + 0.3 + No + Corr + + + + OMEGA(21,21) + 0.15 + No + Corr + + + + OMEGA(22,20) + 0.1 + No + Corr + + + + OMEGA(22,21) + 0.05 + No + Corr + + + + OMEGA(22,22) + 0.3 + No + Corr + + + + OMEGA(23,23) + 0.2 + No + Corr + + + + OMEGA(24,23) + 0.3 + No + Corr + + + + OMEGA(24,24) + 0.15 + No + Corr + + + + OMEGA(25,23) + 0.1 + No + Corr + + + + OMEGA(25,24) + 0.05 + No + Corr + + + + OMEGA(25,25) + 0.3 + No + Corr + + + + OMEGA(26,26) + 6 + Yes + + + + + OMEGA(27,26) + 0.005 + Yes + + + + + OMEGA(27,27) + 0.3 + Yes + + + + + OMEGA(28,26) + 0.001 + Yes + + + + + OMEGA(28,27) + 0.002 + Yes + + + + + OMEGA(28,28) + 0.1 + Yes + + @@ -224,6 +607,36 @@ No Prop-Add covariance, Additive error variance + + SIGMA(3,3) + 1 + Yes + + + + SIGMA(4,4) + 0.036 + No + + + + SIGMA(5,5) + 0.04 + No + Label=Value syntax for SIGMA + + + SIGMA(6,6) + 0.01 + No + diagonal SIGMA + + + SIGMA(7,7) + 0.02 + No + diagonal SIGMA + diff --git a/tests/testthat/_snaps/hyperion-model-knit/model-knit-example1.html b/tests/testthat/_snaps/hyperion-model-knit/model-knit-example1.html index a6b31d40..91f2f7cf 100644 --- a/tests/testthat/_snaps/hyperion-model-knit/model-knit-example1.html +++ b/tests/testthat/_snaps/hyperion-model-knit/model-knit-example1.html @@ -5,12 +5,11 @@ Run Status: Not Run - Dataset: example1.csv Ignore: C -Aliased Columns: CONC → DV, DOSE → AMT +Aliased Columns: DV → CONC, AMT → DOSE diff --git a/tests/testthat/_snaps/hyperion-model-knit/model-knit-iiv-cov.html b/tests/testthat/_snaps/hyperion-model-knit/model-knit-iiv-cov.html index b89ebe7b..a7132b30 100644 --- a/tests/testthat/_snaps/hyperion-model-knit/model-knit-iiv-cov.html +++ b/tests/testthat/_snaps/hyperion-model-knit/model-knit-iiv-cov.html @@ -5,11 +5,12 @@ Run Status: Not Run - Dataset: ../../data/derived/PK_Oral_Ex1.csv Ignore: @ +Aliased Columns: ATFD → TIME, ODV → DV + Theta Parameters diff --git a/tests/testthat/_snaps/hyperion-model-knit/model-knit-iov.html b/tests/testthat/_snaps/hyperion-model-knit/model-knit-iov.html index 15c7430c..b72111b4 100644 --- a/tests/testthat/_snaps/hyperion-model-knit/model-knit-iov.html +++ b/tests/testthat/_snaps/hyperion-model-knit/model-knit-iov.html @@ -5,7 +5,6 @@ Run Status: Not Run - Dataset: test.csv Ignore: @ diff --git a/tests/testthat/_snaps/hyperion-model-knit/model-knit-multiline_table.html b/tests/testthat/_snaps/hyperion-model-knit/model-knit-multiline_table.html index ec3052e5..adeefebc 100644 --- a/tests/testthat/_snaps/hyperion-model-knit/model-knit-multiline_table.html +++ b/tests/testthat/_snaps/hyperion-model-knit/model-knit-multiline_table.html @@ -5,12 +5,11 @@ Run Status: Not Run - Dataset: ../data.csv Dropped Columns: DATE -Aliased Columns: AMT → DOSE +Aliased Columns: DOSE → AMT diff --git a/tests/testthat/_snaps/hyperion-model-knit/model-knit-nmexample.html b/tests/testthat/_snaps/hyperion-model-knit/model-knit-nmexample.html index 1615d741..d5fe8aa0 100644 --- a/tests/testthat/_snaps/hyperion-model-knit/model-knit-nmexample.html +++ b/tests/testthat/_snaps/hyperion-model-knit/model-knit-nmexample.html @@ -5,12 +5,11 @@ Run Status: Not Run - Dataset: example1.csv Ignore: C -Aliased Columns: CONC → DV, DOSE → AMT +Aliased Columns: DV → CONC, AMT → DOSE diff --git a/tests/testthat/_snaps/hyperion-model-print.md b/tests/testthat/_snaps/hyperion-model-print.md index b8f5e1d5..6e2694ad 100644 --- a/tests/testthat/_snaps/hyperion-model-print.md +++ b/tests/testthat/_snaps/hyperion-model-print.md @@ -10,6 +10,7 @@ Run Status: Not Run Dataset: ../../../../data/derived/PK_Oral_Ex1.csv Ignore: @ + Aliased Columns: ATFD→TIME, ODV→DV Output Message @@ -57,10 +58,11 @@ Problem: Some header #2 Run Status: Not Run Dataset: ..\data.csv - Ignore: #, DVID.EQ.3, ID.EQ.3.14 + Ignore: #, DVID.EQ.3, ID.EQ.3.14, DVID.EQ.3, AGE.GE.18, AGE.GT.3, AGE.LT.100, + AGE.LE.65, TYPE.NE.0, TYPE.EQ.1, TYPE.EQN.1, TYPE.NEN.2, TYPE.EQ.1 Records: 200 Dropped Columns: DATE - Aliased Columns: AMT→DOSE + Aliased Columns: DOSE→AMT Output Message @@ -69,32 +71,88 @@ Output - Parameter Initial Lower Upper Fixed Comment - ───────── ─────── ───── ───── ───── ───────────────────── - THETA1 1.5 NA NA No THETA(1) and THETA(2) - THETA2 0.5 0 2 No THETA(1) and THETA(2) - THETA3 2.3 NA NA Yes THETA(3) - THETA4 0.8 NA NA No THETA(4) and THETA(5) - THETA5 0.25 NA NA No THETA(4) and THETA(5) - THETA6 2.3 1 NA Yes THETA(6) - THETA7 0.75 NA NA Yes THETA(7) + Parameter Initial Lower Upper Fixed Comment + ───────── ─────── ───── ───── ───── ───────────────────────────────── + THETA1 1.5 NA NA No THETA(1) and THETA(2) + THETA2 0.5 0 2 No THETA(1) and THETA(2) + THETA3 0.5 -Inf 10 No THETA with -INF lower bound + THETA4 5 0 Inf No THETA with INF upper bound + THETA5 0.1 0 NA No Three identical THETAs + THETA6 0.1 0 NA No Three identical THETAs + THETA7 0.1 0 NA No Three identical THETAs + THETA8 1.5 0 10 No Named THETA + THETA9 0.5 0 NA No NAMES syntax + THETA10 10 0 NA No NAMES syntax + THETA11 2 0 NA No NAMES syntax + THETA12 1.1 1 NA No Three identical THETAs with NAMES + THETA13 1.1 1 NA No Three identical THETAs with NAMES + THETA14 1.1 1 NA No Three identical THETAs with NAMES + THETA15 2.3 NA NA Yes THETA(3) + THETA16 0.8 NA NA No THETA(4) and THETA(5) + THETA17 0.25 NA NA No THETA(4) and THETA(5) + THETA18 2.3 1 NA Yes THETA(6) + THETA19 0.75 NA NA Yes THETA(7) Message -- Omega Parameters -- Output - Parameter Initial Lower Upper Fixed Parametrization Comment - ────────── ─────── ───── ───── ───── ─────────────── ─────────────────────────────────────────── - OMEGA(1,1) 0.04 NA NA No ETA(1) - CL (diagonal) - OMEGA(2,2) 0.17 NA NA No - OMEGA(3,3) 0.2 NA NA No Correlation ETA(2) - V (SD) - OMEGA(4,3) 0.3 NA NA No Correlation ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) - OMEGA(4,4) 0.15 NA NA No Correlation ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) - OMEGA(5,5) 0.2 NA NA No Correlation ETA(2) - V (SD) - OMEGA(6,5) 0.3 NA NA No Correlation ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) - OMEGA(6,6) 0.15 NA NA No Correlation ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) - OMEGA(7,7) 0.1 0 1 Yes ETA(6) - fixed diagonal + Parameter Initial Fixed Parametrization Comment + ──────────── ─────── ───── ─────────────── ─────────────────────────────────────────── + OMEGA(1,1) 0.04 No ETA(1) - CL (diagonal) + OMEGA(2,2) 0.17 No + OMEGA(3,3) 0.2 No Corr ETA(2) - V (SD) + OMEGA(4,3) 0.3 No Corr ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) + OMEGA(4,4) 0.15 No Corr ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) + OMEGA(5,5) 0.2 No Corr ETA(2) - V (SD) + OMEGA(6,5) 0.3 No Corr ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) + OMEGA(6,6) 0.15 No Corr ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) + OMEGA(7,7) 0.01121 Yes + OMEGA(8,7) 0 Yes + OMEGA(8,8) 0.3387 Yes + OMEGA(9,9) 0.1 No + OMEGA(10,9) 0.01 No + OMEGA(10,10) 0.1 No + OMEGA(11,9) 0.01 No + OMEGA(11,10) 0.01 No + OMEGA(11,11) 0.1 No + OMEGA(12,9) 0.01 No + OMEGA(12,10) 0.01 No + OMEGA(12,11) 0.01 No + OMEGA(12,12) 0.1 No + OMEGA(13,13) 0.4 No Label=Value syntax for diagonal + OMEGA(14,14) 0.3 No + OMEGA(15,14) 0.01 No Label=Value syntax in block + OMEGA(15,15) 0.35 No Label=Value syntax in block + OMEGA(16,16) 0.03 No + OMEGA(17,16) 0.01 No + OMEGA(17,17) 0.03 No + OMEGA(18,16) 0.01 No + OMEGA(18,17) 0.01 No + OMEGA(18,18) 0.03 No + OMEGA(19,16) 0.01 No + OMEGA(19,17) 0.01 No + OMEGA(19,18) 0.01 No + OMEGA(19,19) 0.03 No + OMEGA(20,20) 0.2 No Corr + OMEGA(21,20) 0.3 No Corr + OMEGA(21,21) 0.15 No Corr + OMEGA(22,20) 0.1 No Corr + OMEGA(22,21) 0.05 No Corr + OMEGA(22,22) 0.3 No Corr + OMEGA(23,23) 0.2 No Corr + OMEGA(24,23) 0.3 No Corr + OMEGA(24,24) 0.15 No Corr + OMEGA(25,23) 0.1 No Corr + OMEGA(25,24) 0.05 No Corr + OMEGA(25,25) 0.3 No Corr + OMEGA(26,26) 6 Yes + OMEGA(27,26) 0.005 Yes + OMEGA(27,27) 0.3 Yes + OMEGA(28,26) 0.001 Yes + OMEGA(28,27) 0.002 Yes + OMEGA(28,28) 0.1 Yes Message -- Sigma Parameters -- @@ -106,6 +164,11 @@ SIGMA(1,1) 0.01 No Proportional error variance SIGMA(2,1) 0.002 No Prop-Add covariance, Additive error variance SIGMA(2,2) 0.25 No Prop-Add covariance, Additive error variance + SIGMA(3,3) 1 Yes + SIGMA(4,4) 0.036 No + SIGMA(5,5) 0.04 No Label=Value syntax for SIGMA + SIGMA(6,6) 0.01 No diagonal SIGMA + SIGMA(7,7) 0.02 No diagonal SIGMA --- @@ -119,7 +182,7 @@ Run Status: Not Run Dataset: example1.csv Ignore: C - Aliased Columns: CONC→DV, DOSE→AMT + Aliased Columns: DV→CONC, AMT→DOSE Output Message @@ -175,6 +238,7 @@ Run Status: Not Run Dataset: ../../data/derived/PK_Oral_Ex1.csv Ignore: @ + Aliased Columns: ATFD→TIME, ODV→DV Output Message @@ -286,7 +350,7 @@ Run Status: Not Run Dataset: ..\data.csv Dropped Columns: DATE - Aliased Columns: AMT→DOSE + Aliased Columns: DOSE→AMT Output Message @@ -315,7 +379,7 @@ Run Status: Not Run Dataset: example1.csv Ignore: C - Aliased Columns: CONC→DV, DOSE→AMT + Aliased Columns: DV→CONC, AMT→DOSE Output Message diff --git a/tests/testthat/test-model-methods-edge.R b/tests/testthat/test-model-methods-edge.R index f609bbba..6408a5f5 100644 --- a/tests/testthat/test-model-methods-edge.R +++ b/tests/testthat/test-model-methods-edge.R @@ -15,7 +15,7 @@ test_that("format_ignore_condition falls back for unknown operators", { ValueFilter = list( field = "AN01FL", op = "Between", - value = "0" + value = list(Number = 0) ) ) @@ -23,7 +23,7 @@ test_that("format_ignore_condition falls back for unknown operators", { }) test_that("get_theta_parameter_data returns NULL with no parameters", { - x <- list(theta_parameters = list()) + x <- list(thetas = list()) expect_null(get_theta_parameter_data( x, digits = NULL, @@ -50,22 +50,11 @@ test_that("get_random_effect_parameter_data handles BlockSame copying", { blocks <- list( list( structure = list(Block = list(size = 2)), - parametrization = "LogNormal", + fixed = TRUE, + parametrization = "Cholesky", parameters = list( - list( - initial_value = 0.1, - lower_bound = 0, - upper_bound = 1, - is_fixed = FALSE, - comment = "A" - ), - list( - initial_value = 0.2, - lower_bound = 0, - upper_bound = 1, - is_fixed = TRUE, - comment = "B" - ) + list(value = 0.1, comment = "A"), + list(value = 0.2, comment = "B") ) ), list(structure = list(BlockSame = list(size = 2))) From 7b5121001996f339bf96a3e599c8284ab73d273f Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Fri, 10 Apr 2026 11:56:15 -0400 Subject: [PATCH 10/41] clean panic_message --- R/zzz.R | 2 +- src/rust/core/src/lib.rs | 48 +--------------------------------------- 2 files changed, 2 insertions(+), 48 deletions(-) diff --git a/R/zzz.R b/R/zzz.R index 8be006ea..591216cc 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -1,6 +1,6 @@ .onLoad <- function(libname, pkgname) { S7::methods_register() - #set_panic_message() + set_panic_message() # Set default hyperion options if not already set if (is.null(getOption("hyperion.significant_number_display"))) { diff --git a/src/rust/core/src/lib.rs b/src/rust/core/src/lib.rs index 72acacd4..1222b21b 100644 --- a/src/rust/core/src/lib.rs +++ b/src/rust/core/src/lib.rs @@ -38,55 +38,9 @@ pub fn find_config_dir() -> Result> { pharos_find_config_dir().map_to_extendr_err("Failed to find config dir") } -fn extract_clean_message(panic_msg: &str) -> Option { - if let Some(start) = panic_msg.find("called `Result::unwrap()` on an `Err` value: ") { - let content = &panic_msg[start + "called `Result::unwrap()` on an `Err` value: ".len()..]; - if let Some(inner) = content - .strip_prefix("Other(\"") - .and_then(|s| s.strip_suffix("\")")) - { - return Some(inner.replace("\\n", "\n").replace("\\\"", "\"")); - } - return Some(content.to_string()); - } - None -} - -fn is_extendr_location(location: &std::panic::Location<'_>) -> bool { - let file = location.file(); - file.contains("extendr-api") || file.contains("/.cargo/git/checkouts/extendr") -} - #[extendr] pub fn set_panic_message() { - std::panic::set_hook(Box::new(|x| { - // Extract the panic message - let message = if let Some(s) = x.payload().downcast_ref::<&str>() { - s.to_string() - } else if let Some(s) = x.payload().downcast_ref::() { - s.clone() - } else { - "Unknown panic payload type".to_string() - }; - - let clean_message = extract_clean_message(&message); - if let Some(location) = x.location() { - if is_extendr_location(location) { - rprintln!("Error occurred in Hyperion"); - } else { - rprintln!("Error occurred in Hyperion, {}", location); - } - } else { - rprintln!("Error occurred in Hyperion"); - } - - if let Some(clean_message) = clean_message { - let indented_msg = clean_message.replace("\n", "\n\t"); - reprintln!("Reason:\n\t{}\n", indented_msg); - } else { - reprintln!("{message}"); - } - })); + std::panic::set_hook(Box::new(|_| {})); } #[extendr] From 61763cb3168b55e447b1535e3d1f6456d2a0f077 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Mon, 20 Apr 2026 10:59:26 -0400 Subject: [PATCH 11/41] update parsed comments pharos branch --- src/rust/Cargo.lock | 90 ++++++++++--------- src/rust/Cargo.toml | 8 +- src/rust/nmparser/src/lib.rs | 2 +- .../model-knit-everything.html | 36 ++++---- tests/testthat/_snaps/hyperion-model-print.md | 36 ++++---- 5 files changed, 89 insertions(+), 83 deletions(-) diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 12f3ccad..16db0b17 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -46,9 +46,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "blake3" @@ -141,7 +141,7 @@ dependencies = [ [[package]] name = "config" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=pub-comment#4071df7a39ae1f6a08f4cff021287f7dfd7c1c22" +source = "git+https://github.com/a2-ai/pharos?branch=parsed-comments#32529d44bbde2cc6545e3f84a821a3f3fb18079b" dependencies = [ "anyhow", "fs-err", @@ -283,8 +283,8 @@ dependencies = [ [[package]] name = "extendr-api" -version = "0.8.1" -source = "git+https://github.com/extendr/extendr?branch=main#8aafb67b77cd112dd6192ff05efa3bade7ea8e8b" +version = "0.9.0" +source = "git+https://github.com/extendr/extendr?branch=main#d800a2de950ba8b7b67902abc2d6d22b803fab2e" dependencies = [ "extendr-ffi", "extendr-macros", @@ -296,13 +296,13 @@ dependencies = [ [[package]] name = "extendr-ffi" -version = "0.8.1" -source = "git+https://github.com/extendr/extendr?branch=main#8aafb67b77cd112dd6192ff05efa3bade7ea8e8b" +version = "0.9.0" +source = "git+https://github.com/extendr/extendr?branch=main#d800a2de950ba8b7b67902abc2d6d22b803fab2e" [[package]] name = "extendr-macros" -version = "0.8.1" -source = "git+https://github.com/extendr/extendr?branch=main#8aafb67b77cd112dd6192ff05efa3bade7ea8e8b" +version = "0.9.0" +source = "git+https://github.com/extendr/extendr?branch=main#d800a2de950ba8b7b67902abc2d6d22b803fab2e" dependencies = [ "lazy_static", "proc-macro2", @@ -638,9 +638,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.94" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ "once_cell", "wasm-bindgen", @@ -660,9 +660,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.184" +version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "libm" @@ -723,7 +723,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "nonmem" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=pub-comment#4071df7a39ae1f6a08f4cff021287f7dfd7c1c22" +source = "git+https://github.com/a2-ai/pharos?branch=parsed-comments#32529d44bbde2cc6545e3f84a821a3f3fb18079b" dependencies = [ "anyhow", "blake3", @@ -749,13 +749,13 @@ dependencies = [ [[package]] name = "nonmem-parser" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=pub-comment#4071df7a39ae1f6a08f4cff021287f7dfd7c1c22" +source = "git+https://github.com/a2-ai/pharos?branch=parsed-comments#32529d44bbde2cc6545e3f84a821a3f3fb18079b" dependencies = [ "anyhow", "distrs", "fs-err", "logos", - "rand 0.9.2", + "rand 0.9.4", "regex", "serde", ] @@ -875,7 +875,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -895,9 +895,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ "portable-atomic", ] @@ -953,9 +953,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -964,9 +964,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -1012,9 +1012,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -1101,7 +1101,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=pub-comment#4071df7a39ae1f6a08f4cff021287f7dfd7c1c22" +source = "git+https://github.com/a2-ai/pharos?branch=parsed-comments#32529d44bbde2cc6545e3f84a821a3f3fb18079b" dependencies = [ "anyhow", "config", @@ -1267,7 +1267,7 @@ dependencies = [ "percent-encoding", "pest", "pest_derive", - "rand 0.8.5", + "rand 0.8.6", "regex", "serde", "serde_json", @@ -1316,9 +1316,9 @@ checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "ucd-trie" @@ -1347,7 +1347,7 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utils" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=pub-comment#4071df7a39ae1f6a08f4cff021287f7dfd7c1c22" +source = "git+https://github.com/a2-ai/pharos?branch=parsed-comments#32529d44bbde2cc6545e3f84a821a3f3fb18079b" dependencies = [ "anyhow", "fs-err", @@ -1380,11 +1380,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -1393,14 +1393,14 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.117" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ "cfg-if", "once_cell", @@ -1411,9 +1411,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.117" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1421,9 +1421,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.117" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ "bumpalo", "proc-macro2", @@ -1434,9 +1434,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.117" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ "unicode-ident", ] @@ -1582,6 +1582,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 9740fb1a..f4bd4767 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -39,10 +39,10 @@ serde = { workspace = true } extendr-api = { git = "https://github.com/extendr/extendr", branch = "main", features = ["serde"] } # Pharos components -nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "pub-comment" } -nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", branch = "pub-comment" } -config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "pub-comment" } -scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "pub-comment" } +nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "parsed-comments" } +nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", branch = "parsed-comments" } +config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "parsed-comments" } +scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "parsed-comments" } # Core utilities anyhow = "1.0.100" diff --git a/src/rust/nmparser/src/lib.rs b/src/rust/nmparser/src/lib.rs index 24a43be4..6f30a0f3 100644 --- a/src/rust/nmparser/src/lib.rs +++ b/src/rust/nmparser/src/lib.rs @@ -28,7 +28,7 @@ pub fn model_to_robj(model: &mut Model, path: impl AsRef) -> Result // Populate parsed_comment fields before serialization if let Some(ct) = get_comment_type() { - model.populate_parsed_comments(ct); + model.parse_comments(ct); } let mut model_robj = to_robj(model).map_to_extendr_err("failed to create Robj from Model")?; diff --git a/tests/testthat/_snaps/hyperion-model-knit/model-knit-everything.html b/tests/testthat/_snaps/hyperion-model-knit/model-knit-everything.html index f905fd81..3d50a1e5 100644 --- a/tests/testthat/_snaps/hyperion-model-knit/model-knit-everything.html +++ b/tests/testthat/_snaps/hyperion-model-knit/model-knit-everything.html @@ -219,42 +219,42 @@ OMEGA(3,3) 0.2 No - Corr + Correlation ETA(2) - V (SD) OMEGA(4,3) 0.3 No - Corr + Correlation ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) OMEGA(4,4) 0.15 No - Corr + Correlation ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) OMEGA(5,5) 0.2 No - Corr + Correlation ETA(2) - V (SD) OMEGA(6,5) 0.3 No - Corr + Correlation ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) OMEGA(6,6) 0.15 No - Corr + Correlation ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) @@ -450,84 +450,84 @@ OMEGA(20,20) 0.2 No - Corr + Correlation OMEGA(21,20) 0.3 No - Corr + Correlation OMEGA(21,21) 0.15 No - Corr + Correlation OMEGA(22,20) 0.1 No - Corr + Correlation OMEGA(22,21) 0.05 No - Corr + Correlation OMEGA(22,22) 0.3 No - Corr + Correlation OMEGA(23,23) 0.2 No - Corr + Correlation OMEGA(24,23) 0.3 No - Corr + Correlation OMEGA(24,24) 0.15 No - Corr + Correlation OMEGA(25,23) 0.1 No - Corr + Correlation OMEGA(25,24) 0.05 No - Corr + Correlation OMEGA(25,25) 0.3 No - Corr + Correlation diff --git a/tests/testthat/_snaps/hyperion-model-print.md b/tests/testthat/_snaps/hyperion-model-print.md index 6e2694ad..20ce6f2a 100644 --- a/tests/testthat/_snaps/hyperion-model-print.md +++ b/tests/testthat/_snaps/hyperion-model-print.md @@ -102,12 +102,12 @@ ──────────── ─────── ───── ─────────────── ─────────────────────────────────────────── OMEGA(1,1) 0.04 No ETA(1) - CL (diagonal) OMEGA(2,2) 0.17 No - OMEGA(3,3) 0.2 No Corr ETA(2) - V (SD) - OMEGA(4,3) 0.3 No Corr ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) - OMEGA(4,4) 0.15 No Corr ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) - OMEGA(5,5) 0.2 No Corr ETA(2) - V (SD) - OMEGA(6,5) 0.3 No Corr ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) - OMEGA(6,6) 0.15 No Corr ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) + OMEGA(3,3) 0.2 No Correlation ETA(2) - V (SD) + OMEGA(4,3) 0.3 No Correlation ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) + OMEGA(4,4) 0.15 No Correlation ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) + OMEGA(5,5) 0.2 No Correlation ETA(2) - V (SD) + OMEGA(6,5) 0.3 No Correlation ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) + OMEGA(6,6) 0.15 No Correlation ETA(2)-ETA(3) correlation, ETA(3) - KA (SD) OMEGA(7,7) 0.01121 Yes OMEGA(8,7) 0 Yes OMEGA(8,8) 0.3387 Yes @@ -135,18 +135,18 @@ OMEGA(19,17) 0.01 No OMEGA(19,18) 0.01 No OMEGA(19,19) 0.03 No - OMEGA(20,20) 0.2 No Corr - OMEGA(21,20) 0.3 No Corr - OMEGA(21,21) 0.15 No Corr - OMEGA(22,20) 0.1 No Corr - OMEGA(22,21) 0.05 No Corr - OMEGA(22,22) 0.3 No Corr - OMEGA(23,23) 0.2 No Corr - OMEGA(24,23) 0.3 No Corr - OMEGA(24,24) 0.15 No Corr - OMEGA(25,23) 0.1 No Corr - OMEGA(25,24) 0.05 No Corr - OMEGA(25,25) 0.3 No Corr + OMEGA(20,20) 0.2 No Correlation + OMEGA(21,20) 0.3 No Correlation + OMEGA(21,21) 0.15 No Correlation + OMEGA(22,20) 0.1 No Correlation + OMEGA(22,21) 0.05 No Correlation + OMEGA(22,22) 0.3 No Correlation + OMEGA(23,23) 0.2 No Correlation + OMEGA(24,23) 0.3 No Correlation + OMEGA(24,24) 0.15 No Correlation + OMEGA(25,23) 0.1 No Correlation + OMEGA(25,24) 0.05 No Correlation + OMEGA(25,25) 0.3 No Correlation OMEGA(26,26) 6 Yes OMEGA(27,26) 0.005 Yes OMEGA(27,27) 0.3 Yes From 6d7d11180bd5ce8c7e8df8a08840143e402ef037 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Mon, 20 Apr 2026 13:57:42 -0400 Subject: [PATCH 12/41] update comments to only use pharos parsed comments --- NAMESPACE | 1 - R/comments-classes.R | 45 - R/comments-parsing.R | 827 ++---------------- R/comments-query.R | 34 +- R/comments-utils.R | 139 --- R/hyperion-package.R | 2 +- R/model-methods.R | 33 +- inst/extdata/models/onecmt/run001.mod | 16 +- inst/extdata/models/onecmt/run001/run001.lst | 16 +- man/format_omega_display_name.Rd | 44 - man/get_model_parameter_info.Rd | 90 +- man/hyperion-package.Rd | 2 +- pharos.toml | 1 + tests/pharos.toml | 2 +- .../audit-knit-run001.html | 10 +- .../audit-knit-run002.html | 4 +- .../audit-knit-run003.html | 4 +- .../audit-knit-run003b1.html | 6 +- tests/testthat/_snaps/hyperion-audit-print.md | 44 +- .../parameter-info-knit-run001.html | 16 +- .../parameter-info-knit-run002.html | 10 +- .../parameter-info-knit-run003.html | 12 +- .../parameter-info-knit-run003b1.html | 14 +- .../_snaps/hyperion-parameter-info-print.md | 92 +- .../summary-knit-run-err.html | 129 --- .../summary-knit-run001.html | 12 +- .../summary-knit-run003.html | 2 +- .../summary-knit-run003b1.html | 2 +- .../summary-knit-run004-running.html | 4 +- .../summary-knit-run004.html | 4 +- .../testthat/_snaps/hyperion-summary-print.md | 131 +-- .../test-comments-classes-validation.R | 110 --- tests/testthat/test-comments-query-edge.R | 10 - tests/testthat/test-extract_raw_omega_parts.R | 93 -- tests/testthat/test-extract_raw_sigma_parts.R | 61 -- tests/testthat/test-extract_raw_theta_parts.R | 62 -- .../testthat/test-format-omega-display-name.R | 125 --- .../testthat/test-get_model_parameter_names.R | 44 - tests/testthat/test-hyperion-summary-knit.R | 15 - tests/testthat/test-hyperion-summary-print.R | 16 - tests/testthat/test-name-lookup-omega.R | 27 - tests/testthat/test-parse_raw_omega_comment.R | 86 -- tests/testthat/test-parse_raw_theta_comment.R | 11 - .../testthat/test-strip-param-prefix-parens.R | 9 - tests/testthat/test-strip-param-prefix.R | 9 - 45 files changed, 248 insertions(+), 2178 deletions(-) delete mode 100644 man/format_omega_display_name.Rd delete mode 100644 tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run-err.html delete mode 100644 tests/testthat/test-extract_raw_omega_parts.R delete mode 100644 tests/testthat/test-extract_raw_sigma_parts.R delete mode 100644 tests/testthat/test-extract_raw_theta_parts.R delete mode 100644 tests/testthat/test-format-omega-display-name.R delete mode 100644 tests/testthat/test-get_model_parameter_names.R delete mode 100644 tests/testthat/test-name-lookup-omega.R delete mode 100644 tests/testthat/test-parse_raw_omega_comment.R delete mode 100644 tests/testthat/test-parse_raw_theta_comment.R delete mode 100644 tests/testthat/test-strip-param-prefix-parens.R delete mode 100644 tests/testthat/test-strip-param-prefix.R diff --git a/NAMESPACE b/NAMESPACE index cb6c4438..740c38eb 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -38,7 +38,6 @@ export(compute_rse) export(copy_model) export(format_hyperion_decimal_string) export(format_hyperion_sigfig_string) -export(format_omega_display_name) export(from_config_relative) export(get_comment) export(get_comment_type) diff --git a/R/comments-classes.R b/R/comments-classes.R index 12e728b2..d92ee85c 100644 --- a/R/comments-classes.R +++ b/R/comments-classes.R @@ -139,49 +139,6 @@ normalize_associated_theta <- function(assoc, theta_names) { ) } -#' Rename duplicate omega names by appending associated_theta -#' -#' When multiple omega comments share the same name, renames ALL of them to -#' `{name}-{associated_theta}` to ensure uniqueness. -#' -#' @param omega List of OmegaComment objects -#' @return Modified list with duplicate names renamed -#' @noRd -rename_duplicate_omega_names <- function(omega) { - if (length(omega) == 0) { - return(omega) - } - - omega_names <- vapply( - omega, - function(c) if (is.null(c@name)) NA_character_ else c@name, - character(1) - ) - - # Find names that appear more than once (excluding NA) - name_counts <- table(omega_names[!is.na(omega_names)]) - dup_names <- names(name_counts[name_counts > 1]) - - if (length(dup_names) == 0) { - return(omega) - } - - lapply(omega, function(comment) { - if (!is.null(comment@name) && comment@name %in% dup_names) { - assoc <- comment@associated_theta - if (!is.null(assoc) && length(assoc) == 1 && nzchar(assoc)) { - new_name <- paste0(comment@name, "-", assoc) - comment@name <- new_name - sources <- attr(comment, "sources") %||% list() - name_source <- sources[["name"]] %||% "default" - sources[["name"]] <- paste0("renamed from ", name_source) - attr(comment, "sources") <- sources - } - } - comment - }) -} - #' Theta parameter comment class #' #' Represents parsed comments for THETA parameters. @@ -487,8 +444,6 @@ ModelComments <- S7::new_class( } } - omega <- rename_duplicate_omega_names(omega) - S7::new_object( S7::S7_object(), theta = theta, diff --git a/R/comments-parsing.R b/R/comments-parsing.R index b56fc541..66ae2fb3 100644 --- a/R/comments-parsing.R +++ b/R/comments-parsing.R @@ -103,79 +103,11 @@ read_model_from_lst_path <- function(mod_path) { #' #' @return A `ModelComments` object containing theta, omega, and sigma comments. #' -#' @section Comment Parsing Modes: -#' The parsing behavior is controlled by the `pharos.toml` configuration file. -#' In the `[nonmem.comments]` section, set `type = "type1"` to enable structured -#' type1 comment parsing. If this setting is absent or set to any other value, -#' raw comment parsing is used (the default). -#' -#' **type1 mode**: Expects comments in a structured format with explicit field -#' delimiters. This mode provides more precise extraction but requires comments -#' to follow the type1 specification. -#' -#' **raw mode** (default): Flexibly parses parameter names, units, and -#' descriptions from free-form comment text. More forgiving but may be less -#' precise for complex comment structures. -#' -#' @section Raw Comment Formats (default): -#' Applies to text after `;` on lines within `$THETA`, `$OMEGA`, and `$SIGMA` -#' blocks. -#' -#' Parsing pipeline in raw mode: -#' extract transform -> strip prefix -> extract unit -> extract name. -#' -#' **THETA/SIGMA** -#' -#' General form: -#' `[PREFIX] NAME [(UNIT)] [TRANSFORM_SEP TRANSFORM]` -#' -#' Common accepted examples: -#' - `CL` -#' - `CL (L/HR)` -#' - `CL [L/HR]` -#' - `CL ;exp` -#' - `CL :LOG` -#' - `CL (L/HR) ;exp` -#' - `THETA1 CL (L/HR) ;exp` -#' - `1: CL (L/HR) ;exp` -#' -#' Notes: -#' - Prefixes are case-insensitive; colon after prefix is optional. -#' - Numeric prefixes like `1`, `1:`, `1-`, `1.` are accepted. -#' - Units are read from `()` or `[]`, and can appear anywhere in the string. -#' - Colon transform form requires leading whitespace (e.g., `CL :EXP`). -#' - THETA strips trailing punctuation from extracted name tokens; SIGMA -#' currently does not. -#' - SIGMA also supports unit in transform segment, e.g. -#' `Name ;Transform (unit)`. -#' -#' **OMEGA** -#' -#' General form: -#' `[PREFIX] NAME_PART [THETA_REF] [TRANSFORM_SEP TRANSFORM]` -#' -#' Common accepted examples: -#' - `IIV-CL :EXP` -#' - `IIV CL ;exp` -#' - `IIV on CL ;exp` -#' - `Corr CL-V ;normal` -#' - `11: IIV CL ;exp` -#' - `OMEGA(1,1): IIV CL ;exp` -#' -#' Notes: -#' - `THETA_REF` may split on `-`, `/`, `:`, `,` into multiple associated -#' thetas. -#' - If full `THETA_REF` matches a known theta name (case-insensitive), it is -#' kept whole (for example, `CL/F`). -#' - Linking words `on`, `for`, `of` are skipped in space-separated forms. -#' -#' **Transform keyword mapping** (case-insensitive): -#' - `exp`, `log`, `lognormal` -> `LogNormal` -#' - `logit` -> `Logit` -#' - `add`, `adderr`, `additive` -> `AddErr` -#' - `logadd`, `logadderr`, `logerr` -> `LogAddErr` -#' - `prop`, `proportional` -> `Proportional` -#' - `identity`, `normal`, `none` -> `Identity` +#' @section Comment Parsing: +#' Comments are parsed by pharos according to the `[nonmem.comments]` section +#' of `pharos.toml`. Set `type = "type1"` for strict structured comments, or +#' `type = "type2"` for a more flexible structured grammar. See pharos +#' documentation for accepted formats. #' #' @seealso [get_parameter_transform()], [get_theta_names()], [get_comment()] #' @export @@ -226,13 +158,8 @@ get_model_parameter_info <- function(mod, lookup_path = NULL) { } param_names <- get_model_parameter_names(mod) - comments_data <- extract_comments(mod) - comments <- parse_comments( - param_names, - comments_data$parsed, - comments_data$raw, - mod_path - ) + parsed_comments <- extract_comments(mod) + comments <- parse_comments(param_names, parsed_comments, mod_path) # Split into theta, omega, sigma theta_comments <- comments[grepl("^THETA", names(comments))] @@ -272,7 +199,7 @@ extract_comments <- function(mod) { result <- extract_block_comments(parsed, raw, mod$sigma_blocks, "SIGMA") - list(parsed = result$parsed, raw = result$raw) + result$parsed } #' Unwrap ParsedRaneffComment enum (Omega/Sigma wrapper) to get the inner @@ -355,167 +282,9 @@ extract_block_comments <- function(parsed, raw, blocks, prefix) { list(parsed = parsed, raw = raw) } -#' Parse comments from model based on comment_type setting -#' @noRd -parse_comments <- function( - param_names, - parsed_comments, - raw_comments, - mod_path -) { - comment_type <- get_comment_type() - - if (identical(comment_type, "type1")) { - parse_type1_comments(param_names, parsed_comments, raw_comments, mod_path) - } else { - parse_raw_comments(param_names, raw_comments, mod_path) - } -} - -# ============================================================================== -# Raw comment parsing (no comment_type set, extract from raw text only) -# ============================================================================== - -#' @noRd -parse_raw_comments <- function(param_names, raw_comments, mod_path) { - nonmem_names <- names(param_names) - - # First pass: parse thetas to collect known theta names - theta_names <- nonmem_names[grepl("^THETA", nonmem_names)] - theta_comments <- lapply(theta_names, function(nonmem_name) { - name <- param_names[[nonmem_name]] - raw <- raw_comments[[nonmem_name]] - parse_raw_theta_comment(nonmem_name, name, raw, mod_path) - }) - names(theta_comments) <- theta_names - - # Collect known theta names for context - known_thetas <- vapply( - theta_comments, - function(c) c@name %||% "", - character(1) - ) - known_thetas <- known_thetas[nzchar(known_thetas)] - - # Second pass: parse omega/sigma with known_thetas context - other_names <- nonmem_names[!grepl("^THETA", nonmem_names)] - other_comments <- lapply(other_names, function(nonmem_name) { - name <- param_names[[nonmem_name]] - raw <- raw_comments[[nonmem_name]] - - if (grepl("^OMEGA", nonmem_name)) { - parse_raw_omega_comment(nonmem_name, name, raw, mod_path, known_thetas) - } else if (grepl("^SIGMA", nonmem_name)) { - parse_raw_sigma_comment(nonmem_name, name, raw, mod_path) - } else { - rlang::abort(paste0("Unknown parameter type: ", nonmem_name)) - } - }) - names(other_comments) <- other_names - - # Combine and preserve original order - comments <- c(theta_comments, other_comments) - comments[nonmem_names] -} - -#' @noRd -parse_raw_theta_comment <- function(nonmem_name, name, raw, mod_path = NULL) { - name <- normalize_comment_name(name) - - unit <- NULL - parameterization <- NULL - - if (!is.null(raw) && nzchar(raw)) { - parts <- extract_raw_theta_parts(raw) - if (is.null(name)) { - name <- parts$name - } - unit <- parts$unit - parameterization <- map_parameterization(parts$parameterization, "THETA") - } - - create_comment_with_sources( - ThetaComment, - theta_fields(), - mod_path, - nonmem_name = nonmem_name, - name = name, - unit = unit, - parameterization = parameterization - ) -} - -#' @noRd -parse_raw_omega_comment <- function( - nonmem_name, - name, - raw, - mod_path = NULL, - known_thetas = NULL -) { - name <- normalize_comment_name(name) - - parameterization <- NULL - associated_theta <- NULL - - if (!is.null(raw) && nzchar(raw)) { - parts <- extract_raw_omega_parts(raw, known_thetas) - if (is.null(name)) { - name <- parts$name - } - parameterization <- map_parameterization(parts$parameterization, "OMEGA") - associated_theta <- parts$associated_theta - } - - create_comment_with_sources( - OmegaComment, - omega_fields(), - mod_path, - nonmem_name = nonmem_name, - name = name, - parameterization = parameterization, - associated_theta = associated_theta - ) -} - +#' Parse structured (typed) comments from model #' @noRd -parse_raw_sigma_comment <- function(nonmem_name, name, raw, mod_path = NULL) { - name <- normalize_comment_name(name) - - unit <- NULL - parameterization <- NULL - - if (!is.null(raw) && nzchar(raw)) { - parts <- extract_raw_sigma_parts(raw) - if (is.null(name)) { - name <- parts$name - } - unit <- parts$unit - parameterization <- map_parameterization(parts$parameterization, "SIGMA") - } - - create_comment_with_sources( - SigmaComment, - sigma_fields(), - mod_path, - nonmem_name = nonmem_name, - name = name, - unit = unit, - parameterization = parameterization - ) -} - -# ============================================================================== -# Type1 comment parsing -# ============================================================================== - -#' @noRd -parse_type1_comments <- function( - param_names, - parsed_comments, - raw_comments, - mod_path -) { +parse_comments <- function(param_names, parsed_comments, mod_path) { nonmem_names <- names(param_names) # First pass: parse thetas to collect known theta names @@ -523,12 +292,10 @@ parse_type1_comments <- function( theta_comments <- lapply(theta_names, function(nonmem_name) { name <- param_names[[nonmem_name]] parsed <- parsed_comments[[nonmem_name]] - raw <- raw_comments[[nonmem_name]] - parse_type1_theta_comment(nonmem_name, name, parsed, raw, mod_path) + parse_typed_theta_comment(nonmem_name, name, parsed, mod_path) }) names(theta_comments) <- theta_names - # Collect known theta names for context known_thetas <- vapply( theta_comments, function(c) c@name %||% "", @@ -541,82 +308,67 @@ parse_type1_comments <- function( other_comments <- lapply(other_names, function(nonmem_name) { name <- param_names[[nonmem_name]] parsed <- parsed_comments[[nonmem_name]] - raw <- raw_comments[[nonmem_name]] if (grepl("^OMEGA", nonmem_name)) { - parse_type1_omega_comment( + parse_typed_omega_comment( nonmem_name, name, parsed, - raw, mod_path, known_thetas ) } else if (grepl("^SIGMA", nonmem_name)) { - parse_type1_sigma_comment(nonmem_name, name, parsed, raw, mod_path) + parse_typed_sigma_comment(nonmem_name, name, parsed, mod_path) } else { rlang::abort(paste0("Unknown parameter type: ", nonmem_name)) } }) names(other_comments) <- other_names - # Combine and preserve original order comments <- c(theta_comments, other_comments) comments[nonmem_names] } #' @noRd -parse_type1_theta_comment <- function( - nonmem_name, - name, - parsed, - raw, - mod_path -) { +parse_typed_theta_comment <- function(nonmem_name, name, parsed, mod_path) { name <- normalize_comment_name(name) unit <- NULL parameterization <- NULL - # Try to extract from parsed comment - if (!is.null(parsed) && !is.null(parsed$Type1)) { - type1 <- parsed$Type1 - - if (!is.null(type1$WithUnit)) { - if (is.null(name)) { - name <- type1$WithUnit$parameter - } - if (is.null(unit)) { + if (!is.null(parsed)) { + if (!is.null(parsed$Type1)) { + type1 <- parsed$Type1 + if (!is.null(type1$WithUnit)) { + if (is.null(name)) { + name <- type1$WithUnit$parameter + } unit <- type1$WithUnit$unit - } - if (is.null(parameterization)) { parameterization <- map_parameterization( type1$WithUnit$parametrization, "THETA" ) - } - } else if (!is.null(type1$Type)) { - if (is.null(name)) { - name <- type1$Type$typ - } - if (is.null(parameterization)) { + } else if (!is.null(type1$Type)) { + if (is.null(name)) { + name <- type1$Type$typ + } parameterization <- map_parameterization( type1$Type$parameterization, "THETA" ) + } else if (!is.null(type1$Covariate)) { + if (is.null(name)) name <- type1$Covariate$parameter + } + } else if (!is.null(parsed$Type2)) { + type2 <- parsed$Type2 + if (is.null(name)) { + name <- type2$name } - } else if (!is.null(type1$Covariate)) { - if (is.null(name)) name <- type1$Covariate$parameter - } else if (is.character(type1)) { - if (is.null(name)) name <- extract_name_from_raw(type1) + unit <- type2$unit + parameterization <- type2$parameterization } } - # Fallback: extract from raw comment - if (is.null(name) && !is.null(raw) && nzchar(raw)) { - name <- extract_name_from_raw(raw) - } - create_comment_with_sources( ThetaComment, theta_fields(), @@ -644,11 +396,10 @@ is_diagonal_omega <- function(nonmem_name) { } #' @noRd -parse_type1_omega_comment <- function( +parse_typed_omega_comment <- function( nonmem_name, name, parsed, - raw, mod_path, known_thetas = NULL ) { @@ -657,68 +408,29 @@ parse_type1_omega_comment <- function( parameterization <- NULL associated_theta <- NULL - # Parse name format: "OM1 (CL)" to extract associated_theta + # Pharos formats @name as "Name (theta_ref)"; keep it verbatim but also + # extract associated_theta from the parens for downstream queries. if (!is.null(name) && grepl("\\(.*\\)", name)) { theta_part <- gsub(".*\\((.+)\\).*", "\\1", name) - # Use split_theta_reference for context-aware splitting associated_theta <- split_theta_reference(theta_part, known_thetas) - name <- trimws(gsub("\\s*\\(.*\\)\\s*$", "", name)) } - # Try to extract from parsed comment - if (!is.null(parsed) && !is.null(parsed$Type1)) { - type1 <- parsed$Type1 - - if (is.character(type1)) { - # Type1$Unknown: raw string stored directly - if (is.null(name)) { - parsed_raw <- extract_raw_omega_parts(type1, known_thetas) - name <- parsed_raw$name - if (is.null(associated_theta)) { - associated_theta <- parsed_raw$associated_theta - } - if (is.null(parameterization)) { - parameterization <- map_parameterization( - parsed_raw$parameterization, - "OMEGA" - ) - } - } - } else { - # Omega style: name, theta_name, parameterization - if (is.null(name)) { - name <- type1$name - } + if (!is.null(parsed)) { + if (!is.null(parsed$Type1)) { + type1 <- parsed$Type1 if (is.null(associated_theta)) { associated_theta <- type1$theta_name } - if (is.null(parameterization)) { - parameterization <- map_parameterization( - type1$parameterization, - "OMEGA" - ) - } - } - } - - # Fallback: extract from raw comment - if ( - (is.null(name) || is.null(associated_theta)) && - !is.null(raw) && - nzchar(raw) - ) { - parsed_raw <- extract_raw_omega_parts(raw, known_thetas) - if (is.null(name)) { - name <- parsed_raw$name - } - if (is.null(associated_theta)) { - associated_theta <- parsed_raw$associated_theta - } - if (is.null(parameterization)) { parameterization <- map_parameterization( - parsed_raw$parameterization, + type1$parameterization, "OMEGA" ) + } else if (!is.null(parsed$Type2)) { + type2 <- parsed$Type2 + if (is.null(associated_theta)) { + associated_theta <- type2$raw_theta_refs + } + parameterization <- type2$parameterization } } @@ -734,65 +446,29 @@ parse_type1_omega_comment <- function( } #' @noRd -parse_type1_sigma_comment <- function( - nonmem_name, - name, - parsed, - raw, - mod_path -) { +parse_typed_sigma_comment <- function(nonmem_name, name, parsed, mod_path) { name <- normalize_comment_name(name) unit <- NULL parameterization <- NULL - # Try to extract from parsed comment - if (!is.null(parsed) && !is.null(parsed$Type1)) { - type1 <- parsed$Type1 - - if (is.character(type1)) { - # Type1$Unknown: raw string stored directly - parsed_raw <- extract_raw_sigma_parts(type1) - if (is.null(name)) { - name <- parsed_raw$name - } - if (is.null(unit)) { - unit <- parsed_raw$unit - } - if (is.null(parameterization)) { - parameterization <- map_parameterization( - parsed_raw$parameterization, - "SIGMA" - ) - } - } else { - # Sigma style: name, parameterization + if (!is.null(parsed)) { + if (!is.null(parsed$Type1)) { + type1 <- parsed$Type1 if (is.null(name)) { name <- type1$name } - if (is.null(parameterization)) { - parameterization <- map_parameterization( - type1$parameterization, - "SIGMA" - ) - } - } - } - - # Fallback: extract from raw comment - if (!is.null(raw) && nzchar(raw)) { - parsed_raw <- extract_raw_sigma_parts(raw) - if (is.null(name)) { - name <- parsed_raw$name - } - if (is.null(unit)) { - unit <- parsed_raw$unit - } - if (is.null(parameterization)) { parameterization <- map_parameterization( - parsed_raw$parameterization, + type1$parameterization, "SIGMA" ) + } else if (!is.null(parsed$Type2)) { + type2 <- parsed$Type2 + if (is.null(name)) { + name <- type2$name + } + unit <- type2$unit + parameterization <- type2$parameterization } } @@ -807,255 +483,6 @@ parse_type1_sigma_comment <- function( ) } -#' Extract name from raw comment string -#' -#' Finds the first alphanumeric word, skipping leading pure numbers. -#' -#' @param raw Character string of the raw comment -#' @return Character string of the extracted name, or NULL if none found -#' @noRd -extract_name_from_raw <- function(raw) { - if (is.null(raw) || !nzchar(trimws(raw))) { - return(NULL) - } - - words <- strsplit(trimws(raw), "\\s+")[[1]] - idx <- find_first_name_idx(words) - - if (!is.na(idx)) { - return(words[idx]) - } - - NULL -} - -#' Extract parameterization suffix from raw comment -#' -#' Handles formats: "; exp", ";exp", " :EXP" -#' -#' @param raw Character string of the raw comment -#' @return Named list with remaining raw string and parameterization -#' @noRd -extract_parameterization_suffix <- function(raw) { - parameterization <- NULL - - if (grepl(";", raw)) { - parts <- strsplit(raw, ";")[[1]] - raw <- trimws(parts[1]) - if (length(parts) >= 2) { - param_part <- trimws(parts[2]) - if (nzchar(param_part)) { - parameterization <- param_part - } - } - } else if (grepl("\\s+:", raw)) { - match <- regmatches(raw, regexec("\\s+:\\s*(.+)\\s*$", raw))[[1]] - if (length(match) >= 2) { - parameterization <- trimws(match[2]) - raw <- trimws(sub("\\s+:\\s*.+\\s*$", "", raw)) - } - } - - list(raw = raw, parameterization = parameterization) -} - -#' Strip parameter prefix from raw comment -#' -#' Removes THETAn:, OMEGAn:, OMEGA(n,n):, SIGMAn:, SIGMA(n,n): prefixes -#' -#' @param raw Character string of the raw comment -#' @return Character string with prefix removed -#' @noRd -strip_param_prefix <- function(raw) { - # Colon after parameter identifier is optional - raw <- gsub("^THETA\\(\\d+\\):?\\s*", "", raw, ignore.case = TRUE) - raw <- gsub("^THETA\\d+:?\\s*", "", raw, ignore.case = TRUE) - raw <- gsub("^OMEGA\\d+:?\\s*", "", raw, ignore.case = TRUE) - raw <- gsub("^OMEGA\\(\\d+,\\d+\\):?\\s*", "", raw, ignore.case = TRUE) - raw <- gsub("^SIGMA\\(\\d+\\):?\\s*", "", raw, ignore.case = TRUE) - raw <- gsub("^SIGMA\\d+:?\\s*", "", raw, ignore.case = TRUE) - raw <- gsub("^SIGMA\\(\\d+,\\d+\\):?\\s*", "", raw, ignore.case = TRUE) - # Also handle bare number prefix like "1:", "1-", "1.", or "1 " - raw <- gsub("^\\d+[-:.]?\\s*", "", raw) - raw -} - -#' Find first word containing letters -#' -#' @param words Character vector of words -#' @return Index of first word with letters, or NA if none found -#' @noRd -find_first_name_idx <- function(words) { - for (i in seq_along(words)) { - if (grepl("[A-Za-z]", words[i])) { - return(i) - } - } - NA_integer_ -} - -#' Extract components from raw theta comment string -#' -#' Parses comments like "THETA1: CL (L/day) ; exp" or "CL (L/day)" -#' -#' @param raw Character string of the raw comment -#' @return Named list with name, unit, and parameterization -#' @noRd -extract_raw_theta_parts <- function(raw) { - result <- list(name = NULL, unit = NULL, parameterization = NULL) - - if (is.null(raw) || !nzchar(trimws(raw))) { - return(result) - } - - raw <- trimws(raw) - - # Extract parameterization suffix - extracted <- extract_parameterization_suffix(raw) - raw <- extracted$raw - result$parameterization <- extracted$parameterization - - # Strip parameter prefix - raw <- strip_param_prefix(raw) - - # Extract unit from parentheses or brackets (anywhere in the string) - unit_parts <- extract_unit_anywhere(raw) - raw <- unit_parts$raw - if (is.null(result$unit)) { - result$unit <- unit_parts$unit - } - - # Find name (first word with letters) - if (nzchar(raw)) { - words <- strsplit(raw, "\\s+")[[1]] - idx <- find_first_name_idx(words) - if (!is.na(idx)) { - # Strip trailing punctuation (comma, period, etc.) - result$name <- gsub("[,.:;]+$", "", words[idx]) - } - } - - result -} - -#' Check if a bracketed string looks like a unit -#' -#' @param value Character string inside () or [] -#' @return TRUE if value looks like a unit -#' @noRd -is_unit_like <- function(value) { - if (is.null(value) || !nzchar(trimws(value))) { - return(FALSE) - } - - value <- trimws(value) - - if (grepl(",", value, fixed = TRUE)) { - return(FALSE) - } - - # Allow alphabetic abbreviations like CONC or prop - if (grepl("^[A-Za-z0-9_]+$", value)) { - return(TRUE) - } - - # Allow unit-ish tokens that include separators or digits - grepl("[/0-9%^]", value) -} - -#' Find the matching closing delimiter for a balanced segment -#' -#' @param raw Character string to scan -#' @param open_pos Position of the opening delimiter -#' @param open_char Opening delimiter character -#' @param close_char Closing delimiter character -#' @return Integer position of matching closing delimiter, or NA -#' @noRd -find_balanced_close <- function(raw, open_pos, open_char, close_char) { - raw_len <- nchar(raw) - depth <- 0L - - for (i in seq.int(open_pos, raw_len)) { - char_i <- substr(raw, i, i) - if (identical(char_i, open_char)) { - depth <- depth + 1L - } else if (identical(char_i, close_char)) { - depth <- depth - 1L - if (depth == 0L) { - return(i) - } - } - } - - NA_integer_ -} - -#' Extract unit from parentheses or brackets anywhere in the string -#' -#' @param raw Character string of the raw comment -#' @return Named list with raw (unit removed) and unit -#' @noRd -extract_unit_anywhere <- function(raw) { - result <- list(raw = raw, unit = NULL) - - if (is.null(raw) || !nzchar(trimws(raw))) { - return(result) - } - - pos <- 1 - raw_len <- nchar(raw) - - while (pos <= raw_len) { - paren_start <- regexpr("(", substr(raw, pos, raw_len), fixed = TRUE)[1] - bracket_start <- regexpr("[", substr(raw, pos, raw_len), fixed = TRUE)[1] - - paren_pos <- if (paren_start == -1) Inf else pos + paren_start - 1 - bracket_pos <- if (bracket_start == -1) Inf else pos + bracket_start - 1 - - if (is.infinite(paren_pos) && is.infinite(bracket_pos)) { - return(result) - } - - if (paren_pos <= bracket_pos) { - close_pos <- find_balanced_close(raw, paren_pos, "(", ")") - if (is.na(close_pos)) { - return(result) - } - candidate <- substr(raw, paren_pos + 1, close_pos - 1) - if (is_unit_like(candidate)) { - result$unit <- candidate - result$raw <- trimws( - paste0( - substr(raw, 1, paren_pos - 1), - substr(raw, close_pos + 1, raw_len) - ) - ) - return(result) - } - pos <- close_pos + 1 - } else { - close_pos <- find_balanced_close(raw, bracket_pos, "[", "]") - if (is.na(close_pos)) { - return(result) - } - candidate <- substr(raw, bracket_pos + 1, close_pos - 1) - if (is_unit_like(candidate)) { - result$unit <- candidate - result$raw <- trimws( - paste0( - substr(raw, 1, bracket_pos - 1), - substr(raw, close_pos + 1, raw_len) - ) - ) - return(result) - } - pos <- close_pos + 1 - } - } - - result -} - #' Split theta reference into associated thetas #' #' Splits on separators unless the string matches a known theta name (case-insensitive). @@ -1101,139 +528,3 @@ split_theta_reference <- function(theta_ref, known_thetas = NULL) { theta_ref } - -#' Extract components from raw omega comment string -#' -#' Parses comments like "OM1 CL", "OM1 CL :EXP", "OMEGA1: CL ; exp", or "OM2,1 CL-VC". -#' Builds composite names (e.g., "IIV CL" -> "IIV-CL") and extracts associated thetas. -#' -#' @param raw Character string of the raw comment -#' @param known_thetas Character vector of known theta names for context-aware splitting -#' @return Named list with name, associated_theta (character vector), and parameterization -#' @noRd -extract_raw_omega_parts <- function(raw, known_thetas = NULL) { - result <- list(name = NULL, associated_theta = NULL, parameterization = NULL) - - if (is.null(raw) || !nzchar(trimws(raw))) { - return(result) - } - - raw <- trimws(raw) - - # Extract parameterization suffix - extracted <- extract_parameterization_suffix(raw) - raw <- extracted$raw - result$parameterization <- extracted$parameterization - - # Strip parameter prefix - raw <- strip_param_prefix(raw) - - # Split remaining into words and find first word with letters - - words <- strsplit(raw, "\\s+")[[1]] - idx <- find_first_name_idx(words) - - if (is.na(idx)) { - return(result) - } - - first_word <- words[idx] - prefix <- NULL - theta_ref <- NULL - - # First token may itself be an off-diagonal theta pair - # (e.g., "CL/F-V2/F", "CL/F:V2/F", or "CL/F,V2/F"). - pair <- split_theta_reference(first_word, known_thetas) - has_known_pair <- !is.null(known_thetas) && - length(known_thetas) > 0 && - length(pair) == 2 && - !any(is.na(pair)) && - all(tolower(pair) %in% tolower(known_thetas)) - - if (has_known_pair) { - result$associated_theta <- pair - } else if (grepl("-", first_word)) { - # Check if first word already contains a hyphen (e.g., "IIV-CL", "Corr-CL-V") - # Split on first hyphen only to get prefix - hyphen_pos <- regexpr("-", first_word) - prefix <- substr(first_word, 1, hyphen_pos - 1) - theta_ref <- substr(first_word, hyphen_pos + 1, nchar(first_word)) - } else { - # First word is the prefix, look for theta reference in subsequent words - prefix <- first_word - - # Find theta reference, skipping linking words like "on", "for" - linking_words <- c("on", "for", "of") - theta_idx <- idx + 1 - while ( - theta_idx <= length(words) && - tolower(words[theta_idx]) %in% linking_words - ) { - theta_idx <- theta_idx + 1 - } - - if (theta_idx <= length(words)) { - theta_ref <- words[theta_idx] - } - } - - # Store prefix as name, theta reference separately in associated_theta - result$name <- prefix - if ( - is.null(result$associated_theta) && !is.null(theta_ref) && nzchar(theta_ref) - ) { - result$associated_theta <- split_theta_reference(theta_ref, known_thetas) - } - - result -} - -#' Extract components from raw sigma comment string -#' -#' Parses comments like "SIG1", "PropErr", "AddErr (ng/mL) :PROP", -#' or "SIGMA1: PropErr ; prop" -#' -#' @param raw Character string of the raw comment -#' @return Named list with name, unit, and parameterization -#' @noRd -extract_raw_sigma_parts <- function(raw) { - result <- list(name = NULL, unit = NULL, parameterization = NULL) - - if (is.null(raw) || !nzchar(trimws(raw))) { - return(result) - } - - raw <- trimws(raw) - - # Extract parameterization suffix using shared helper - extracted <- extract_parameterization_suffix(raw) - raw <- extracted$raw - result$parameterization <- extracted$parameterization - - if (!is.null(result$parameterization) && nzchar(result$parameterization)) { - unit_parts <- extract_unit_anywhere(result$parameterization) - if (!is.null(unit_parts$unit)) { - result$unit <- unit_parts$unit - result$parameterization <- unit_parts$raw - } - } - - # Strip parameter prefix using shared helper - raw <- strip_param_prefix(raw) - - # Extract unit from parentheses or brackets (anywhere in the string) - unit_parts <- extract_unit_anywhere(raw) - raw <- unit_parts$raw - if (is.null(result$unit)) { - result$unit <- unit_parts$unit - } - - # Find name (first word with letters) using shared helper - words <- strsplit(raw, "\\s+")[[1]] - idx <- find_first_name_idx(words) - if (!is.na(idx)) { - result$name <- words[idx] - } - - result -} diff --git a/R/comments-query.R b/R/comments-query.R index 0a349efe..9304ac8f 100644 --- a/R/comments-query.R +++ b/R/comments-query.R @@ -76,16 +76,14 @@ build_comment_lookup <- function(model_comments) { #' @noRd resolve_comment <- function(model_comments, nm, kind = NULL) { - lookup_nm <- sub(" \\(.*\\)$", "", nm) - resolve_in_kind <- function(kind_name) { comments <- S7::prop(model_comments, tolower(kind_name)) - comment <- comments[[lookup_nm]] + comment <- comments[[nm]] if (!is.null(comment)) { return(comment) } for (cmt in comments) { - if (!is.null(cmt@name) && identical(cmt@name, lookup_nm)) { + if (!is.null(cmt@name) && identical(cmt@name, nm)) { return(cmt) } } @@ -109,7 +107,7 @@ resolve_comment <- function(model_comments, nm, kind = NULL) { if (length(matches) > 1) { rlang::abort(paste0( "Ambiguous parameter name '", - lookup_nm, + nm, "'. Provide kind." )) } @@ -256,22 +254,14 @@ get_parameter_names <- function(x, lookup_path = NULL) { } model_comments <- x - extract_row <- function(comment, include_associated_theta = FALSE) { + extract_row <- function(comment) { name_val <- comment@name %||% NA_character_ - # For omega: build composite name with associated_theta (avoiding duplicates) if ( - include_associated_theta && - !is.null(comment@associated_theta) && - length(comment@associated_theta) > 0 + is.na(name_val) && + S7::S7_inherits(comment, OmegaComment) && + length(comment@associated_theta %||% character()) > 0 ) { - if (is.na(name_val)) { - name_val <- paste(comment@associated_theta, collapse = ", ") - } else { - name_val <- format_omega_display_name( - name_val, - comment@associated_theta - ) - } + name_val <- paste(comment@associated_theta, collapse = ", ") } data.frame( name = name_val, @@ -289,13 +279,7 @@ get_parameter_names <- function(x, lookup_path = NULL) { } for (nm in names(model_comments@omega)) { - rows <- c( - rows, - list(extract_row( - model_comments@omega[[nm]], - include_associated_theta = TRUE - )) - ) + rows <- c(rows, list(extract_row(model_comments@omega[[nm]]))) row_names <- c(row_names, nm) } diff --git a/R/comments-utils.R b/R/comments-utils.R index c8dd7cb4..5f1bdf5c 100644 --- a/R/comments-utils.R +++ b/R/comments-utils.R @@ -1,142 +1,3 @@ -#' Format omega display name, avoiding duplicate theta info -#' -#' Builds a display name for omega parameters by appending associated theta -#' information, but only if that information isn't already present in the name. -#' This prevents duplication like "IIV-CL (CL)" when the omega was already -#' renamed to include the theta. -#' -#' @param name The omega parameter name (e.g., "IIV-CL" or "IIV") -#' @param associated_theta Character vector of associated theta names -#' @param theta_labels Optional named vector mapping theta names to display -#' labels. If provided, uses labels for the suffix; otherwise uses theta names. -#' -#' @return The formatted display name with theta info appended only if missing -#' -#' @examples -#' # Theta already in name - no duplication -#' format_omega_display_name("IIV-CL", "CL") -#' # Returns: "IIV-CL" -#' -#' # Theta not in name - appends it -#' format_omega_display_name("IIV", "CL") -#' # Returns: "IIV CL" -#' -#' # Multiple thetas -#' format_omega_display_name("IIV", c("CL", "V")) -#' # Returns: "IIV CL, V" -#' -#' # With custom labels -#' format_omega_display_name("IIV", "CL", c(CL = "Clearance")) -#' # Returns: "IIV Clearance" -#' -#' @keywords internal -#' @export -format_omega_display_name <- function( - name, - associated_theta, - theta_labels = NULL -) { - if (is.null(associated_theta) || length(associated_theta) == 0) { - return(name) - } - - # Determine what labels to use for checking and appending - if (!is.null(theta_labels)) { - labels_to_use <- vapply( - associated_theta, - function(theta) { - if (theta %in% names(theta_labels)) { - theta_labels[[theta]] - } else { - theta - } - }, - character(1) - ) - } else { - labels_to_use <- associated_theta - } - - # Extract root for display (strip prefixes/suffixes, preserve case) - extract_root <- function(term) { - # Strip TV/ETA prefix - term <- sub("^(TV|ETA)", "", term, ignore.case = TRUE) - # Strip / suffix (e.g., /F) - sub("/[A-Za-z]$", "", term) - } - - # Normalize for matching (root + lowercase) - normalize_for_match <- function(term) { - tolower(extract_root(term)) - } - - # Normalize into "token space" form for phrase matching - # Keeps / as part of tokens, converts other non-alphanumeric to spaces - normalize_for_phrase <- function(x) { - x <- tolower(x) - x <- gsub("[^a-z0-9/]+", " ", x) - x <- gsub("\\s+", " ", x) - trimws(x) - } - - # Prepare padded omega name for phrase-safe matching - omega_phrase_normalized <- normalize_for_phrase(name) - omega_padded <- paste0(" ", omega_phrase_normalized, " ") - - # Split omega name into segments on hyphen and space (preserve / within segments) - omega_segments_raw <- unlist(strsplit(name, "[- ]+")) - omega_segments_normalized <- vapply( - omega_segments_raw, - normalize_for_match, - character(1) - ) - - # Check which thetas are already present in the name - theta_already_present <- vapply( - seq_along(associated_theta), - function(i) { - theta <- associated_theta[i] - label <- labels_to_use[i] - - # Normalize theta and label for comparison - theta_normalized <- normalize_for_match(theta) - label_normalized <- normalize_for_match(label) - - # Phrase-safe checks using padded boundaries - # Handles multi-word labels like "CL/F Scaling" without matching substrings - label_phrase <- normalize_for_phrase(label) - theta_phrase <- normalize_for_phrase(theta) - - if (grepl(paste0(" ", label_phrase, " "), omega_padded, fixed = TRUE)) { - return(TRUE) - } - if (grepl(paste0(" ", theta_phrase, " "), omega_padded, fixed = TRUE)) { - return(TRUE) - } - - # Fall back to segment-based matching - if (theta_normalized %in% omega_segments_normalized) { - return(TRUE) - } - if (label_normalized %in% omega_segments_normalized) { - return(TRUE) - } - - FALSE - }, - logical(1) - ) - - # Only append missing thetas (keep original name for display) - missing_labels <- labels_to_use[!theta_already_present] - if (length(missing_labels) > 0) { - theta_str <- paste(missing_labels, collapse = ", ") - paste0(name, " ", theta_str) - } else { - name - } -} - #' Convert comment list to data frame with values #' @param comments Named list of comment objects #' @param fields Character vector of field names to extract diff --git a/R/hyperion-package.R b/R/hyperion-package.R index b9df26df..194b23f0 100644 --- a/R/hyperion-package.R +++ b/R/hyperion-package.R @@ -84,7 +84,7 @@ #' \itemize{ #' \item [init()] - Initialize pharos with config file path #' \item [get_pharos_config()] - Get current pharos configuration -#' \item [get_comment_type()] - Get comment parsing mode (raw or type1) +#' \item [get_comment_type()] - Get comment parsing mode (type1 or type2) #' \item [use_type1_comments()] - Configure pharos.toml for type1 comment parsing #' } #' diff --git a/R/model-methods.R b/R/model-methods.R index 5af2e544..e72ad735 100644 --- a/R/model-methods.R +++ b/R/model-methods.R @@ -105,41 +105,10 @@ summary.hyperion_nonmem_model <- function( )) } - summary_obj <- get_model_summary( + get_model_summary( object, hide_off_diagonal_params = hide_off_diagonal_params ) - - comment_type <- get_comment_type() - is_type1 <- !is.null(comment_type) && - is.character(comment_type) && - length(comment_type) == 1 && - identical(tolower(comment_type), "type1") - - if (!is_type1 && !is.null(summary_obj$parameters)) { - tryCatch( - { - info <- get_model_parameter_info(object) - name_map <- get_parameter_names(info) - - if (nrow(name_map) > 0 && "name" %in% names(summary_obj$parameters)) { - nonmem_names <- summary_obj$parameters$name - mapped <- name_map[nonmem_names, "name", drop = TRUE] - replace_idx <- !is.na(mapped) & nzchar(mapped) - summary_obj$parameters$name[replace_idx] <- mapped[replace_idx] - } - }, - error = function(e) { - rlang::warn(c( - "Could not apply parameter names from model comments.", - "i" = "Falling back to NONMEM parameter names.", - "x" = conditionMessage(e) - )) - } - ) - } - - summary_obj } #' Build summary object for a running model diff --git a/inst/extdata/models/onecmt/run001.mod b/inst/extdata/models/onecmt/run001.mod index 6108172d..f8bce08f 100644 --- a/inst/extdata/models/onecmt/run001.mod +++ b/inst/extdata/models/onecmt/run001.mod @@ -26,18 +26,18 @@ IPRED = F Y = IPRED * (1 + EPS(1)) + EPS(2) $THETA -(0, 1) ; 1. TVCL (L/hr) -(0, 30) ; 2. TVV (L) -(0, 1) ; 3. TVKA (1/hr) +(0, 1) ;TVCL (L/hr) +(0, 30) ;TVV (L) +(0, 1) ;TVKA (1/hr) $OMEGA -0.1 ; 1. OM1 TVCL :EXP -0.1 ; 2. OM2 TVV :EXP -0.1 FIX ; 3. OM3 TVKA :EXP +0.1 ;OM1 TVCL :EXP +0.1 ;OM2 TVV :EXP +0.1 FIX ;OM3 TVKA :EXP $SIGMA -0.04 ; 1. Proportional error (variance, 20% CV) -0.01 FIX ; 2. Additive error (variance, 0.01 mg/L SD) +0.04 ;Proportional error (variance, 20% CV) +0.01 FIX ;Additive error (variance, 0.01 mg/L SD) $ESTIMATION METHOD=1 INTERACTION MAXEVAL=9999 PRINT=5 $COV PRINT=E MATRIX = R diff --git a/inst/extdata/models/onecmt/run001/run001.lst b/inst/extdata/models/onecmt/run001/run001.lst index 8fcd1cbf..024bc277 100644 --- a/inst/extdata/models/onecmt/run001/run001.lst +++ b/inst/extdata/models/onecmt/run001/run001.lst @@ -27,18 +27,18 @@ IPRED = F Y = IPRED * (1 + EPS(1)) + EPS(2) $THETA -(0, 1) ; 1. TVCL (L/hr) -(0, 30) ; 2. TVV (L) -(0, 1) ; 3. TVKA (1/hr) +(0, 1) ;TVCL (L/hr) +(0, 30) ;TVV (L) +(0, 1) ;TVKA (1/hr) $OMEGA -0.1 ; 1. OM1 TVCL :EXP -0.1 ; 2. OM2 TVV :EXP -0.1 FIX ; 3. OM3 TVKA :EXP +0.1 ;OM1 TVCL :EXP +0.1 ;OM2 TVV :EXP +0.1 FIX ;OM3 TVKA :EXP $SIGMA -0.04 ; 1. Proportional error (variance, 20% CV) -0.01 FIX ; 2. Additive error (variance, 0.01 mg/L SD) +0.04 ;Proportional error (variance, 20% CV) +0.01 FIX ;Additive error (variance, 0.01 mg/L SD) $ESTIMATION METHOD=1 INTERACTION MAXEVAL=9999 PRINT=5 $COV PRINT=E MATRIX = R diff --git a/man/format_omega_display_name.Rd b/man/format_omega_display_name.Rd deleted file mode 100644 index 0dba344a..00000000 --- a/man/format_omega_display_name.Rd +++ /dev/null @@ -1,44 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/comments-utils.R -\name{format_omega_display_name} -\alias{format_omega_display_name} -\title{Format omega display name, avoiding duplicate theta info} -\usage{ -format_omega_display_name(name, associated_theta, theta_labels = NULL) -} -\arguments{ -\item{name}{The omega parameter name (e.g., "IIV-CL" or "IIV")} - -\item{associated_theta}{Character vector of associated theta names} - -\item{theta_labels}{Optional named vector mapping theta names to display -labels. If provided, uses labels for the suffix; otherwise uses theta names.} -} -\value{ -The formatted display name with theta info appended only if missing -} -\description{ -Builds a display name for omega parameters by appending associated theta -information, but only if that information isn't already present in the name. -This prevents duplication like "IIV-CL (CL)" when the omega was already -renamed to include the theta. -} -\examples{ -# Theta already in name - no duplication -format_omega_display_name("IIV-CL", "CL") -# Returns: "IIV-CL" - -# Theta not in name - appends it -format_omega_display_name("IIV", "CL") -# Returns: "IIV CL" - -# Multiple thetas -format_omega_display_name("IIV", c("CL", "V")) -# Returns: "IIV CL, V" - -# With custom labels -format_omega_display_name("IIV", "CL", c(CL = "Clearance")) -# Returns: "IIV Clearance" - -} -\keyword{internal} diff --git a/man/get_model_parameter_info.Rd b/man/get_model_parameter_info.Rd index b773a876..bbc1236d 100644 --- a/man/get_model_parameter_info.Rd +++ b/man/get_model_parameter_info.Rd @@ -27,92 +27,12 @@ For model objects sourced from \code{.mod}/\code{.ctl} files: \item otherwise (\code{"not_run"}/\code{"running"}), metadata is read from the model file } } -\section{Comment Parsing Modes}{ +\section{Comment Parsing}{ -The parsing behavior is controlled by the \code{pharos.toml} configuration file. -In the \verb{[nonmem.comments]} section, set \code{type = "type1"} to enable structured -type1 comment parsing. If this setting is absent or set to any other value, -raw comment parsing is used (the default). - -\strong{type1 mode}: Expects comments in a structured format with explicit field -delimiters. This mode provides more precise extraction but requires comments -to follow the type1 specification. - -\strong{raw mode} (default): Flexibly parses parameter names, units, and -descriptions from free-form comment text. More forgiving but may be less -precise for complex comment structures. -} - -\section{Raw Comment Formats (default)}{ - -Applies to text after \verb{;} on lines within \verb{$THETA}, \verb{$OMEGA}, and \verb{$SIGMA} -blocks. - -Parsing pipeline in raw mode: -extract transform -> strip prefix -> extract unit -> extract name. - -\strong{THETA/SIGMA} - -General form: -\verb{[PREFIX] NAME [(UNIT)] [TRANSFORM_SEP TRANSFORM]} - -Common accepted examples: -\itemize{ -\item \code{CL} -\item \code{CL (L/HR)} -\item \code{CL [L/HR]} -\item \verb{CL ;exp} -\item \code{CL :LOG} -\item \verb{CL (L/HR) ;exp} -\item \verb{THETA1 CL (L/HR) ;exp} -\item \verb{1: CL (L/HR) ;exp} -} - -Notes: -\itemize{ -\item Prefixes are case-insensitive; colon after prefix is optional. -\item Numeric prefixes like \code{1}, \verb{1:}, \verb{1-}, \code{1.} are accepted. -\item Units are read from \verb{()} or \verb{[]}, and can appear anywhere in the string. -\item Colon transform form requires leading whitespace (e.g., \code{CL :EXP}). -\item THETA strips trailing punctuation from extracted name tokens; SIGMA -currently does not. -\item SIGMA also supports unit in transform segment, e.g. -\verb{Name ;Transform (unit)}. -} - -\strong{OMEGA} - -General form: -\verb{[PREFIX] NAME_PART [THETA_REF] [TRANSFORM_SEP TRANSFORM]} - -Common accepted examples: -\itemize{ -\item \code{IIV-CL :EXP} -\item \verb{IIV CL ;exp} -\item \verb{IIV on CL ;exp} -\item \verb{Corr CL-V ;normal} -\item \verb{11: IIV CL ;exp} -\item \verb{OMEGA(1,1): IIV CL ;exp} -} - -Notes: -\itemize{ -\item \code{THETA_REF} may split on \code{-}, \code{/}, \code{:}, \verb{,} into multiple associated -thetas. -\item If full \code{THETA_REF} matches a known theta name (case-insensitive), it is -kept whole (for example, \code{CL/F}). -\item Linking words \code{on}, \code{for}, \code{of} are skipped in space-separated forms. -} - -\strong{Transform keyword mapping} (case-insensitive): -\itemize{ -\item \code{exp}, \code{log}, \code{lognormal} -> \code{LogNormal} -\item \code{logit} -> \code{Logit} -\item \code{add}, \code{adderr}, \code{additive} -> \code{AddErr} -\item \code{logadd}, \code{logadderr}, \code{logerr} -> \code{LogAddErr} -\item \code{prop}, \code{proportional} -> \code{Proportional} -\item \code{identity}, \code{normal}, \code{none} -> \code{Identity} -} +Comments are parsed by pharos according to the \verb{[nonmem.comments]} section +of \code{pharos.toml}. Set \code{type = "type1"} for strict structured comments, or +\code{type = "type2"} for a more flexible structured grammar. See pharos +documentation for accepted formats. } \seealso{ diff --git a/man/hyperion-package.Rd b/man/hyperion-package.Rd index 2c36ef7f..521aab7f 100644 --- a/man/hyperion-package.Rd +++ b/man/hyperion-package.Rd @@ -104,7 +104,7 @@ Functions for pharos configuration: \itemize{ \item \code{\link[=init]{init()}} - Initialize pharos with config file path \item \code{\link[=get_pharos_config]{get_pharos_config()}} - Get current pharos configuration -\item \code{\link[=get_comment_type]{get_comment_type()}} - Get comment parsing mode (raw or type1) +\item \code{\link[=get_comment_type]{get_comment_type()}} - Get comment parsing mode (type1 or type2) \item \code{\link[=use_type1_comments]{use_type1_comments()}} - Configure pharos.toml for type1 comment parsing } } diff --git a/pharos.toml b/pharos.toml index f18e919b..4bf966f6 100644 --- a/pharos.toml +++ b/pharos.toml @@ -22,6 +22,7 @@ num_cpus = 4 timeout = 2147483647 [nonmem.comments] +type = "type2" error_on_invalid = false [nonmem.summary] diff --git a/tests/pharos.toml b/tests/pharos.toml index f41f9d85..4bf966f6 100644 --- a/tests/pharos.toml +++ b/tests/pharos.toml @@ -22,7 +22,7 @@ num_cpus = 4 timeout = 2147483647 [nonmem.comments] -type = "type1" +type = "type2" error_on_invalid = false [nonmem.summary] diff --git a/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run001.html b/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run001.html index d79b89e8..3367138e 100644 --- a/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run001.html +++ b/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run001.html @@ -22,7 +22,7 @@ extdata/models/onecmt/run001/run001.lst default default - default + extdata/models/onecmt/run001/run001.lst default @@ -30,7 +30,7 @@ extdata/models/onecmt/run001/run001.lst default default - default + extdata/models/onecmt/run001/run001.lst default @@ -38,7 +38,7 @@ extdata/models/onecmt/run001/run001.lst default default - default + extdata/models/onecmt/run001/run001.lst default @@ -105,7 +105,7 @@ SIGMA(1,1) - extdata/models/onecmt/run001/run001.lst + default default default default @@ -113,7 +113,7 @@ SIGMA(2,2) - extdata/models/onecmt/run001/run001.lst + default default default default diff --git a/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run002.html b/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run002.html index 836d6447..f8a4c4ec 100644 --- a/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run002.html +++ b/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run002.html @@ -105,7 +105,7 @@ SIGMA(1,1) - extdata/models/onecmt/run002/run002.lst + default default default default @@ -113,7 +113,7 @@ SIGMA(2,2) - extdata/models/onecmt/run002/run002.lst + default default default default diff --git a/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run003.html b/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run003.html index 0052256f..b31b3c81 100644 --- a/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run003.html +++ b/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run003.html @@ -113,7 +113,7 @@ SIGMA(1,1) - extdata/models/onecmt/run003/run003.lst + default default default default @@ -121,7 +121,7 @@ SIGMA(2,2) - extdata/models/onecmt/run003/run003.lst + default default default default diff --git a/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run003b1.html b/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run003b1.html index 21a6cfaa..0bd67ea1 100644 --- a/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run003b1.html +++ b/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run003b1.html @@ -27,7 +27,7 @@ THETA2 - extdata/models/onecmt/run003b1/run003b1.lst + default default default default @@ -121,7 +121,7 @@ SIGMA(1,1) - extdata/models/onecmt/run003b1/run003b1.lst + default default default default @@ -129,7 +129,7 @@ SIGMA(2,2) - extdata/models/onecmt/run003b1/run003b1.lst + default default default default diff --git a/tests/testthat/_snaps/hyperion-audit-print.md b/tests/testthat/_snaps/hyperion-audit-print.md index e9e6078c..830449e4 100644 --- a/tests/testthat/_snaps/hyperion-audit-print.md +++ b/tests/testthat/_snaps/hyperion-audit-print.md @@ -14,11 +14,11 @@ Output - parameter name display description unit parameterization - ───────── ─────────────────────────────────────── ─────── ─────────── ─────── ──────────────── - THETA1 extdata/models/onecmt/run001/run001.lst default default default default - THETA2 extdata/models/onecmt/run001/run001.lst default default default default - THETA3 extdata/models/onecmt/run001/run001.lst default default default default + parameter name display description unit parameterization + ───────── ─────────────────────────────────────── ─────── ─────────── ─────────────────────────────────────── ──────────────── + THETA1 extdata/models/onecmt/run001/run001.lst default default extdata/models/onecmt/run001/run001.lst default + THETA2 extdata/models/onecmt/run001/run001.lst default default extdata/models/onecmt/run001/run001.lst default + THETA3 extdata/models/onecmt/run001/run001.lst default default extdata/models/onecmt/run001/run001.lst default Message -- Omega Sources -- @@ -36,10 +36,10 @@ Output - parameter name display description unit parameterization - ────────── ─────────────────────────────────────── ─────── ─────────── ─────── ──────────────── - SIGMA(1,1) extdata/models/onecmt/run001/run001.lst default default default default - SIGMA(2,2) extdata/models/onecmt/run001/run001.lst default default default default + parameter name display description unit parameterization + ────────── ─────── ─────── ─────────── ─────── ──────────────── + SIGMA(1,1) default default default default default + SIGMA(2,2) default default default default default --- @@ -79,10 +79,10 @@ Output - parameter name display description unit parameterization - ────────── ─────────────────────────────────────── ─────── ─────────── ─────── ──────────────── - SIGMA(1,1) extdata/models/onecmt/run002/run002.lst default default default default - SIGMA(2,2) extdata/models/onecmt/run002/run002.lst default default default default + parameter name display description unit parameterization + ────────── ─────── ─────── ─────────── ─────── ──────────────── + SIGMA(1,1) default default default default default + SIGMA(2,2) default default default default default --- @@ -123,10 +123,10 @@ Output - parameter name display description unit parameterization - ────────── ─────────────────────────────────────── ─────── ─────────── ─────── ──────────────── - SIGMA(1,1) extdata/models/onecmt/run003/run003.lst default default default default - SIGMA(2,2) extdata/models/onecmt/run003/run003.lst default default default default + parameter name display description unit parameterization + ────────── ─────── ─────── ─────────── ─────── ──────────────── + SIGMA(1,1) default default default default default + SIGMA(2,2) default default default default default --- @@ -147,7 +147,7 @@ parameter name display description unit parameterization ───────── ─────────────────────────────────────────── ─────── ─────────── ─────────────────────────────────────────── ──────────────── THETA1 extdata/models/onecmt/run003b1/run003b1.lst default default extdata/models/onecmt/run003b1/run003b1.lst default - THETA2 extdata/models/onecmt/run003b1/run003b1.lst default default default default + THETA2 default default default default default THETA3 extdata/models/onecmt/run003b1/run003b1.lst default default extdata/models/onecmt/run003b1/run003b1.lst default THETA4 extdata/models/onecmt/run003b1/run003b1.lst default default extdata/models/onecmt/run003b1/run003b1.lst default @@ -168,8 +168,8 @@ Output - parameter name display description unit parameterization - ────────── ─────────────────────────────────────────── ─────── ─────────── ─────── ──────────────── - SIGMA(1,1) extdata/models/onecmt/run003b1/run003b1.lst default default default default - SIGMA(2,2) extdata/models/onecmt/run003b1/run003b1.lst default default default default + parameter name display description unit parameterization + ────────── ─────── ─────── ─────────── ─────── ──────────────── + SIGMA(1,1) default default default default default + SIGMA(2,2) default default default default default diff --git a/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run001.html b/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run001.html index 437ff15a..22b95fa8 100644 --- a/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run001.html +++ b/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run001.html @@ -22,7 +22,7 @@ TVCL NA NA - NA + L/hr NA @@ -30,7 +30,7 @@ TVV NA NA - NA + L NA @@ -38,7 +38,7 @@ TVKA NA NA - NA + 1/hr NA @@ -62,7 +62,7 @@ OMEGA(1,1) - OM1 + OM1 (TVCL) NA NA LogNormal @@ -70,7 +70,7 @@ OMEGA(2,2) - OM2 + OM2 (TVV) NA NA LogNormal @@ -78,7 +78,7 @@ OMEGA(3,3) - OM3 + OM3 (TVKA) NA NA LogNormal @@ -105,7 +105,7 @@ SIGMA(1,1) - Proportional + NA NA NA NA @@ -113,7 +113,7 @@ SIGMA(2,2) - Additive + NA NA NA NA diff --git a/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run002.html b/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run002.html index cfdb4047..22b95fa8 100644 --- a/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run002.html +++ b/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run002.html @@ -62,7 +62,7 @@ OMEGA(1,1) - OM1 + OM1 (TVCL) NA NA LogNormal @@ -70,7 +70,7 @@ OMEGA(2,2) - OM2 + OM2 (TVV) NA NA LogNormal @@ -78,7 +78,7 @@ OMEGA(3,3) - OM3 + OM3 (TVKA) NA NA LogNormal @@ -105,7 +105,7 @@ SIGMA(1,1) - SIG1 + NA NA NA NA @@ -113,7 +113,7 @@ SIGMA(2,2) - SIG2 + NA NA NA NA diff --git a/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run003.html b/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run003.html index 85f39e42..e00f60bc 100644 --- a/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run003.html +++ b/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run003.html @@ -62,7 +62,7 @@ OMEGA(1,1) - OM1 + OM1 (TVCL) NA NA LogNormal @@ -70,7 +70,7 @@ OMEGA(2,1) - OM1,2 + OM1,2 (TVCL:TVV) NA NA LogNormal @@ -78,7 +78,7 @@ OMEGA(2,2) - OM2 + OM2 (TVV) NA NA LogNormal @@ -86,7 +86,7 @@ OMEGA(3,3) - OM3 + OM3 (TVKA) NA NA LogNormal @@ -113,7 +113,7 @@ SIGMA(1,1) - SIG1 + NA NA NA NA @@ -121,7 +121,7 @@ SIGMA(2,2) - SIG2 + NA NA NA NA diff --git a/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run003b1.html b/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run003b1.html index 78e91bc1..11eacafa 100644 --- a/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run003b1.html +++ b/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run003b1.html @@ -27,7 +27,7 @@ THETA2 - WT-on-CL + NA NA NA NA @@ -70,7 +70,7 @@ OMEGA(1,1) - OM1 + OM1 (TVCL) NA NA LogNormal @@ -78,7 +78,7 @@ OMEGA(2,1) - OM1,2 + OM1,2 (TVCL:TVV) NA NA LogNormal @@ -86,7 +86,7 @@ OMEGA(2,2) - OM2 + OM2 (TVV) NA NA LogNormal @@ -94,7 +94,7 @@ OMEGA(3,3) - OM3 + OM3 (TVKA) NA NA LogNormal @@ -121,7 +121,7 @@ SIGMA(1,1) - SIG1 + NA NA NA NA @@ -129,7 +129,7 @@ SIGMA(2,2) - SIG2 + NA NA NA NA diff --git a/tests/testthat/_snaps/hyperion-parameter-info-print.md b/tests/testthat/_snaps/hyperion-parameter-info-print.md index 8522d882..d003fe82 100644 --- a/tests/testthat/_snaps/hyperion-parameter-info-print.md +++ b/tests/testthat/_snaps/hyperion-parameter-info-print.md @@ -16,30 +16,30 @@ parameter name display description unit parameterization ───────── ──── ─────── ─────────── ──── ──────────────── - THETA1 TVCL NA NA NA NA - THETA2 TVV NA NA NA NA - THETA3 TVKA NA NA NA NA + THETA1 TVCL NA NA L/hr NA + THETA2 TVV NA NA L NA + THETA3 TVKA NA NA 1/hr NA Message -- Omega Parameters -- Output - parameter name display description parameterization associated_theta - ────────── ──── ─────── ─────────── ──────────────── ──────────────── - OMEGA(1,1) OM1 NA NA LogNormal TVCL - OMEGA(2,2) OM2 NA NA LogNormal TVV - OMEGA(3,3) OM3 NA NA LogNormal TVKA + parameter name display description parameterization associated_theta + ────────── ────────── ─────── ─────────── ──────────────── ──────────────── + OMEGA(1,1) OM1 (TVCL) NA NA LogNormal TVCL + OMEGA(2,2) OM2 (TVV) NA NA LogNormal TVV + OMEGA(3,3) OM3 (TVKA) NA NA LogNormal TVKA Message -- Sigma Parameters -- Output - parameter name display description unit parameterization - ────────── ──────────── ─────── ─────────── ──── ──────────────── - SIGMA(1,1) Proportional NA NA NA NA - SIGMA(2,2) Additive NA NA NA NA + parameter name display description unit parameterization + ────────── ──── ─────── ─────────── ──── ──────────────── + SIGMA(1,1) NA NA NA NA NA + SIGMA(2,2) NA NA NA NA NA --- @@ -68,11 +68,11 @@ Output - parameter name display description parameterization associated_theta - ────────── ──── ─────── ─────────── ──────────────── ──────────────── - OMEGA(1,1) OM1 NA NA LogNormal TVCL - OMEGA(2,2) OM2 NA NA LogNormal TVV - OMEGA(3,3) OM3 NA NA LogNormal TVKA + parameter name display description parameterization associated_theta + ────────── ────────── ─────── ─────────── ──────────────── ──────────────── + OMEGA(1,1) OM1 (TVCL) NA NA LogNormal TVCL + OMEGA(2,2) OM2 (TVV) NA NA LogNormal TVV + OMEGA(3,3) OM3 (TVKA) NA NA LogNormal TVKA Message -- Sigma Parameters -- @@ -81,8 +81,8 @@ parameter name display description unit parameterization ────────── ──── ─────── ─────────── ──── ──────────────── - SIGMA(1,1) SIG1 NA NA NA NA - SIGMA(2,2) SIG2 NA NA NA NA + SIGMA(1,1) NA NA NA NA NA + SIGMA(2,2) NA NA NA NA NA --- @@ -111,12 +111,12 @@ Output - parameter name display description parameterization associated_theta - ────────── ───── ─────── ─────────── ──────────────── ──────────────── - OMEGA(1,1) OM1 NA NA LogNormal TVCL - OMEGA(2,1) OM1,2 NA NA LogNormal TVCL, TVV - OMEGA(2,2) OM2 NA NA LogNormal TVV - OMEGA(3,3) OM3 NA NA LogNormal TVKA + parameter name display description parameterization associated_theta + ────────── ──────────────── ─────── ─────────── ──────────────── ──────────────── + OMEGA(1,1) OM1 (TVCL) NA NA LogNormal TVCL + OMEGA(2,1) OM1,2 (TVCL:TVV) NA NA LogNormal TVCL, TVV + OMEGA(2,2) OM2 (TVV) NA NA LogNormal TVV + OMEGA(3,3) OM3 (TVKA) NA NA LogNormal TVKA Message -- Sigma Parameters -- @@ -125,8 +125,8 @@ parameter name display description unit parameterization ────────── ──── ─────── ─────────── ──── ──────────────── - SIGMA(1,1) SIG1 NA NA NA NA - SIGMA(2,2) SIG2 NA NA NA NA + SIGMA(1,1) NA NA NA NA NA + SIGMA(2,2) NA NA NA NA NA --- @@ -144,24 +144,24 @@ Output - parameter name display description unit parameterization - ───────── ──────── ─────── ─────────── ──── ──────────────── - THETA1 TVCL NA NA L/hr NA - THETA2 WT-on-CL NA NA NA NA - THETA3 TVV NA NA L NA - THETA4 TVKA NA NA 1/hr NA + parameter name display description unit parameterization + ───────── ──── ─────── ─────────── ──── ──────────────── + THETA1 TVCL NA NA L/hr NA + THETA2 NA NA NA NA NA + THETA3 TVV NA NA L NA + THETA4 TVKA NA NA 1/hr NA Message -- Omega Parameters -- Output - parameter name display description parameterization associated_theta - ────────── ───── ─────── ─────────── ──────────────── ──────────────── - OMEGA(1,1) OM1 NA NA LogNormal TVCL - OMEGA(2,1) OM1,2 NA NA LogNormal TVCL, TVV - OMEGA(2,2) OM2 NA NA LogNormal TVV - OMEGA(3,3) OM3 NA NA LogNormal TVKA + parameter name display description parameterization associated_theta + ────────── ──────────────── ─────── ─────────── ──────────────── ──────────────── + OMEGA(1,1) OM1 (TVCL) NA NA LogNormal TVCL + OMEGA(2,1) OM1,2 (TVCL:TVV) NA NA LogNormal TVCL, TVV + OMEGA(2,2) OM2 (TVV) NA NA LogNormal TVV + OMEGA(3,3) OM3 (TVKA) NA NA LogNormal TVKA Message -- Sigma Parameters -- @@ -170,8 +170,8 @@ parameter name display description unit parameterization ────────── ──── ─────── ─────────── ──── ──────────────── - SIGMA(1,1) SIG1 NA NA NA NA - SIGMA(2,2) SIG2 NA NA NA NA + SIGMA(1,1) NA NA NA NA NA + SIGMA(2,2) NA NA NA NA NA # hyperion_nonmem_parameter_info works for unrun model input @@ -201,11 +201,11 @@ Output - parameter name display description parameterization associated_theta - ────────── ──── ─────── ─────────── ──────────────── ──────────────── - OMEGA(1,1) OM1 NA NA LogNormal CL/F - OMEGA(2,2) OM2 NA NA LogNormal VC/F - OMEGA(3,3) OM3 NA NA LogNormal KA + parameter name display description parameterization associated_theta + ────────── ──────── ─────── ─────────── ──────────────── ──────────────── + OMEGA(1,1) OM1 (CL) NA NA LogNormal CL/F + OMEGA(2,2) OM2 (VC) NA NA LogNormal VC/F + OMEGA(3,3) OM3 (KA) NA NA LogNormal KA Message -- Sigma Parameters -- diff --git a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run-err.html b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run-err.html deleted file mode 100644 index 16dd1966..00000000 --- a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run-err.html +++ /dev/null @@ -1,129 +0,0 @@ - -Model Summary: run-err - -Problem: Base one-compartment oral absorption model created from pharos see run004_metadata.json for details. - -Records: 240 | Observations: 210 | Subjects: 30 - -Final OFV: -103.3 - - -Estimation Methods - -- First Order Conditional Estimation with Interaction - - -Heuristic Checks - -[OK] Minimization Successful - -[] Covariance Step Not Run - -[] Eigenvalue Check Not Available - -[OK] No Parameters Near Boundary - -[OK] No Hessian Resets - - - -Theta Parameters - - - - - - - - - - - - - - - - - - - - - - - - - - -
Parameter Estimate Fixed
THETA1 1.241 No
THETA2 40.86 No
THETA3 1.241 No
- - - -Omega Parameters - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Parameter Random Effect Estimate Shrinkage (%) Fixed
OMEGA(1,1) ETA1 0.1309 18.98 No
OMEGA(2,2) ETA2 0.1357 4.909 No
OMEGA(3,3) ETA3 0.1 NA Yes
- - - -Sigma Parameters - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Parameter Random Effect Estimate Shrinkage (%) Fixed
SIGMA(1,1) EPS1 0.03635 15.28 No
SIGMA(2,2) EPS2 0.01 NA Yes
- diff --git a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run001.html b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run001.html index 2634ffa6..6c2dc7fd 100644 --- a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run001.html +++ b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run001.html @@ -42,21 +42,21 @@ - THETA1 + TVCL 1.241 0.1129 9.096 No - THETA2 + TVV 40.86 3 7.343 No - THETA3 + TVKA 1.241 0.108 8.697 @@ -83,7 +83,7 @@ - OMEGA(1,1) + OM1 (TVCL) ETA1 0.1309 0.05481 @@ -92,7 +92,7 @@ No - OMEGA(2,2) + OM2 (TVV) ETA2 0.1357 0.03891 @@ -101,7 +101,7 @@ No - OMEGA(3,3) + OM3 (TVKA) ETA3 0.1 NA diff --git a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run003.html b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run003.html index a839fcec..e559179f 100644 --- a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run003.html +++ b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run003.html @@ -92,7 +92,7 @@ No - OMEGA(2,1) + OM1,2 (TVCL:TVV) ETA1:ETA2 0.07454 0.03134 diff --git a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run003b1.html b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run003b1.html index 456dcb90..56c7401a 100644 --- a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run003b1.html +++ b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run003b1.html @@ -84,7 +84,7 @@ No - OMEGA(2,1) + OM1,2 (TVCL:TVV) ETA1:ETA2 0.07218 NA diff --git a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004-running.html b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004-running.html index 478d7b7f..6154aa8f 100644 --- a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004-running.html +++ b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004-running.html @@ -152,8 +152,8 @@ GRD.TVCL. GRD.TVV. GRD.TVKA. - GRD.ETA1. - GRD.ETA2. + GRD.OM1..TVCL.. + GRD.OM2..TVV.. GRD.EPS1. GRD.7. GRD.8. diff --git a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004.html b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004.html index 478d7b7f..6154aa8f 100644 --- a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004.html +++ b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004.html @@ -152,8 +152,8 @@ GRD.TVCL. GRD.TVV. GRD.TVKA. - GRD.ETA1. - GRD.ETA2. + GRD.OM1..TVCL.. + GRD.OM2..TVV.. GRD.EPS1. GRD.7. GRD.8. diff --git a/tests/testthat/_snaps/hyperion-summary-print.md b/tests/testthat/_snaps/hyperion-summary-print.md index 9060835e..c2cccd5e 100644 --- a/tests/testthat/_snaps/hyperion-summary-print.md +++ b/tests/testthat/_snaps/hyperion-summary-print.md @@ -32,9 +32,9 @@ Parameter Estimate SE RSE (%) Fixed ───────── ──────── ────── ─────── ───── - THETA1 1.241 0.1129 9.096 No - THETA2 40.86 3 7.343 No - THETA3 1.241 0.108 8.697 No + TVCL 1.241 0.1129 9.096 No + TVV 40.86 3 7.343 No + TVKA 1.241 0.108 8.697 No Message -- Omega Parameters -- @@ -43,9 +43,9 @@ Parameter Random Effect Estimate SE RSE (%) Shrinkage (%) Fixed ────────── ───────────── ──────── ─────── ─────── ───────────── ───── - OMEGA(1,1) ETA1 0.1309 0.05481 41.86 18.98 No - OMEGA(2,2) ETA2 0.1357 0.03891 28.68 4.909 No - OMEGA(3,3) ETA3 0.1 NA NA NA Yes + OM1 (TVCL) ETA1 0.1309 0.05481 41.86 18.98 No + OM2 (TVV) ETA2 0.1357 0.03891 28.68 4.909 No + OM3 (TVKA) ETA3 0.1 NA NA NA Yes Message -- Sigma Parameters -- @@ -160,12 +160,12 @@ Output - Parameter Random Effect Estimate SE RSE (%) Shrinkage (%) Fixed - ────────── ───────────── ──────── ─────── ─────── ───────────── ───── - OM1 (TVCL) ETA1 0.1223 0.05036 41.16 13.14 No - OMEGA(2,1) ETA1:ETA2 0.07454 0.03134 42.04 NA No - OM2 (TVV) ETA2 0.1239 0.03675 29.66 4.631 No - OM3 (TVKA) ETA3 0.1224 0.05628 45.97 24.34 No + Parameter Random Effect Estimate SE RSE (%) Shrinkage (%) Fixed + ──────────────── ───────────── ──────── ─────── ─────── ───────────── ───── + OM1 (TVCL) ETA1 0.1223 0.05036 41.16 13.14 No + OM1,2 (TVCL:TVV) ETA1:ETA2 0.07454 0.03134 42.04 NA No + OM2 (TVV) ETA2 0.1239 0.03675 29.66 4.631 No + OM3 (TVKA) ETA3 0.1224 0.05628 45.97 24.34 No Message -- Sigma Parameters -- @@ -221,12 +221,12 @@ Output - Parameter Random Effect Estimate Shrinkage (%) Fixed - ────────── ───────────── ──────── ───────────── ───── - OM1 (TVCL) ETA1 0.1233 13.66 No - OMEGA(2,1) ETA1:ETA2 0.07218 NA No - OM2 (TVV) ETA2 0.1246 4.625 No - OM3 (TVKA) ETA3 0.1239 24.36 No + Parameter Random Effect Estimate Shrinkage (%) Fixed + ──────────────── ───────────── ──────── ───────────── ───── + OM1 (TVCL) ETA1 0.1233 13.66 No + OM1,2 (TVCL:TVV) ETA1:ETA2 0.07218 NA No + OM2 (TVV) ETA2 0.1246 4.625 No + OM3 (TVKA) ETA3 0.1239 24.36 No Message -- Sigma Parameters -- @@ -268,15 +268,15 @@ Output - iteration method GRD.TVCL. GRD.TVV. GRD.TVKA. GRD.ETA1. GRD.ETA2. GRD.EPS1. GRD.7. GRD.8. GRD.9. - ───────── ────── ───────── ──────── ────────── ────────── ─────────── ────────── ────────── ───────── ────────── - 0 FOCE 73.89 -28.82 -39.24 -19.1 -0.2895 -15.42 -3.366 -25.08 -1.02 - 5 FOCE 0.726 -0.4298 1.646 -1.356 -0.1662 -1.269 -1.461 -5.953 -0.471 - 10 FOCE -0.292 -0.1409 -0.6106 0.221 -0.1557 0.5171 0.04447 1.326 0.00219 - 15 FOCE -0.476 0.05529 0.08351 0.05463 -0.002578 0.3325 -0.03349 -0.8686 -0.1977 - 20 FOCE 0.07577 0.1036 0.01967 -0.01282 0.0001055 -0.02382 -0.03252 0.08144 0.05834 - 25 FOCE -0.09361 -0.2471 -0.06087 -0.002033 0.0001151 -0.007342 -0.005697 -0.006275 -0.002337 - 30 FOCE -0.002697 0.00215 -0.0007652 -0.0003618 0.000001674 -0.0002712 0.00001258 -0.00347 -0.0002455 + iteration method GRD.TVCL. GRD.TVV. GRD.TVKA. GRD.OM1..TVCL.. GRD.OM2..TVV.. GRD.EPS1. GRD.7. GRD.8. GRD.9. + ───────── ────── ───────── ──────── ────────── ─────────────── ────────────── ────────── ────────── ───────── ────────── + 0 FOCE 73.89 -28.82 -39.24 -19.1 -0.2895 -15.42 -3.366 -25.08 -1.02 + 5 FOCE 0.726 -0.4298 1.646 -1.356 -0.1662 -1.269 -1.461 -5.953 -0.471 + 10 FOCE -0.292 -0.1409 -0.6106 0.221 -0.1557 0.5171 0.04447 1.326 0.00219 + 15 FOCE -0.476 0.05529 0.08351 0.05463 -0.002578 0.3325 -0.03349 -0.8686 -0.1977 + 20 FOCE 0.07577 0.1036 0.01967 -0.01282 0.0001055 -0.02382 -0.03252 0.08144 0.05834 + 25 FOCE -0.09361 -0.2471 -0.06087 -0.002033 0.0001151 -0.007342 -0.005697 -0.006275 -0.002337 + 30 FOCE -0.002697 0.00215 -0.0007652 -0.0003618 0.000001674 -0.0002712 0.00001258 -0.00347 -0.0002455 # hyperion.nonmem-summary print works for run005 (not_run) @@ -324,72 +324,13 @@ Output - iteration method GRD.TVCL. GRD.TVV. GRD.TVKA. GRD.ETA1. GRD.ETA2. GRD.EPS1. GRD.7. GRD.8. GRD.9. - ───────── ────── ───────── ──────── ────────── ────────── ─────────── ────────── ────────── ───────── ────────── - 0 FOCE 73.89 -28.82 -39.24 -19.1 -0.2895 -15.42 -3.366 -25.08 -1.02 - 5 FOCE 0.726 -0.4298 1.646 -1.356 -0.1662 -1.269 -1.461 -5.953 -0.471 - 10 FOCE -0.292 -0.1409 -0.6106 0.221 -0.1557 0.5171 0.04447 1.326 0.00219 - 15 FOCE -0.476 0.05529 0.08351 0.05463 -0.002578 0.3325 -0.03349 -0.8686 -0.1977 - 20 FOCE 0.07577 0.1036 0.01967 -0.01282 0.0001055 -0.02382 -0.03252 0.08144 0.05834 - 25 FOCE -0.09361 -0.2471 -0.06087 -0.002033 0.0001151 -0.007342 -0.005697 -0.006275 -0.002337 - 30 FOCE -0.002697 0.00215 -0.0007652 -0.0003618 0.000001674 -0.0002712 0.00001258 -0.00347 -0.0002455 - -# hyperion.nonmem-summary print fails gracefully for ill-formatted comments - - Code - print(mod_sum) - Message - - - -- Model Summary: run-err ------------------------------------------------------ - Problem: Base one-compartment oral absorption model created from pharos see - run004_metadata.json for details. - Records: 240 | Observations: 210 | Subjects: 30 - Final OFV: -103.3 - - -- Estimation Methods -- - - * First Order Conditional Estimation with Interaction - - -- Heuristic Checks -- - - [OK] Minimization Successful - [!] Covariance Step Not Run - [!] Eigenvalue Check Not Available - [OK] No Parameters Near Boundary - [OK] No Hessian Resets - Output - - Message - - -- Theta Parameters -- - - Output - - Parameter Estimate Fixed - ───────── ──────── ───── - THETA1 1.241 No - THETA2 40.86 No - THETA3 1.241 No - - Message - -- Omega Parameters -- - - Output - - Parameter Random Effect Estimate Shrinkage (%) Fixed - ────────── ───────────── ──────── ───────────── ───── - OMEGA(1,1) ETA1 0.1309 18.98 No - OMEGA(2,2) ETA2 0.1357 4.909 No - OMEGA(3,3) ETA3 0.1 NA Yes - - Message - -- Sigma Parameters -- - - Output - - Parameter Random Effect Estimate Shrinkage (%) Fixed - ────────── ───────────── ──────── ───────────── ───── - SIGMA(1,1) EPS1 0.03635 15.28 No - SIGMA(2,2) EPS2 0.01 NA Yes + iteration method GRD.TVCL. GRD.TVV. GRD.TVKA. GRD.OM1..TVCL.. GRD.OM2..TVV.. GRD.EPS1. GRD.7. GRD.8. GRD.9. + ───────── ────── ───────── ──────── ────────── ─────────────── ────────────── ────────── ────────── ───────── ────────── + 0 FOCE 73.89 -28.82 -39.24 -19.1 -0.2895 -15.42 -3.366 -25.08 -1.02 + 5 FOCE 0.726 -0.4298 1.646 -1.356 -0.1662 -1.269 -1.461 -5.953 -0.471 + 10 FOCE -0.292 -0.1409 -0.6106 0.221 -0.1557 0.5171 0.04447 1.326 0.00219 + 15 FOCE -0.476 0.05529 0.08351 0.05463 -0.002578 0.3325 -0.03349 -0.8686 -0.1977 + 20 FOCE 0.07577 0.1036 0.01967 -0.01282 0.0001055 -0.02382 -0.03252 0.08144 0.05834 + 25 FOCE -0.09361 -0.2471 -0.06087 -0.002033 0.0001151 -0.007342 -0.005697 -0.006275 -0.002337 + 30 FOCE -0.002697 0.00215 -0.0007652 -0.0003618 0.000001674 -0.0002712 0.00001258 -0.00347 -0.0002455 diff --git a/tests/testthat/test-comments-classes-validation.R b/tests/testthat/test-comments-classes-validation.R index 3c1bca06..16a1212c 100644 --- a/tests/testthat/test-comments-classes-validation.R +++ b/tests/testthat/test-comments-classes-validation.R @@ -34,113 +34,3 @@ test_that("ModelComments enforces comment class types", { ) }) -test_that("ModelComments allows unnamed omega duplicates from model files", { - mod_path <- system.file( - "extdata", - "models", - "run-duplicate-omega-names.mod", - package = "hyperion" - ) - mod <- read_model(mod_path) - param_names <- get_model_parameter_names(mod) - comments_data <- hyperion:::extract_comments(mod) - comments <- hyperion:::parse_comments( - param_names, - comments_data$parsed, - comments_data$raw, - mod_path - ) - omega_comments <- comments[grepl("^OMEGA", names(comments))] - - expect_no_error(ModelComments(omega = omega_comments)) -}) - -test_that("ModelComments renames duplicate omega names to name-associated_theta", { - theta1 <- ThetaComment(nonmem_name = "THETA1", name = "CL") - theta2 <- ThetaComment(nonmem_name = "THETA2", name = "V") - theta3 <- ThetaComment(nonmem_name = "THETA3", name = "KA") - - # All three omegas have the same name "IIV" but different associated_theta - omega1 <- OmegaComment( - nonmem_name = "OMEGA(1,1)", - name = "IIV", - associated_theta = "CL" - ) - omega2 <- OmegaComment( - nonmem_name = "OMEGA(2,2)", - name = "IIV", - associated_theta = "V" - ) - omega3 <- OmegaComment( - nonmem_name = "OMEGA(3,3)", - name = "IIV", - associated_theta = "KA" - ) - - # Set up sources to test audit trail - attr(omega1, "sources") <- list(name = "test.lst") - attr(omega2, "sources") <- list(name = "test.lst") - attr(omega3, "sources") <- list(name = "test.lst") - - info <- ModelComments( - theta = list(THETA1 = theta1, THETA2 = theta2, THETA3 = theta3), - omega = list( - `OMEGA(1,1)` = omega1, - `OMEGA(2,2)` = omega2, - `OMEGA(3,3)` = omega3 - ) - ) - - # All names should be renamed to include associated_theta - expect_equal(info@omega[["OMEGA(1,1)"]]@name, "IIV-CL") - expect_equal(info@omega[["OMEGA(2,2)"]]@name, "IIV-V") - expect_equal(info@omega[["OMEGA(3,3)"]]@name, "IIV-KA") - - # Audit should show "renamed from" source - audit <- audit_parameter_info(info) - expect_true(all(grepl("renamed from", audit$omega$name))) -}) - -test_that("raw comment parsing renames duplicate omega names", { - mod_files <- c( - "run-duplicate-omega-names-with-theta.mod", - "run-duplicate-omega-names-space.mod" - ) - - for (mod_file in mod_files) { - mod_path <- system.file("extdata", "models", mod_file, package = "hyperion") - mod <- read_model(mod_path) - param_names <- get_model_parameter_names(mod) - comments_data <- hyperion:::extract_comments(mod) - comments <- hyperion:::parse_comments( - param_names, - comments_data$parsed, - comments_data$raw, - mod_path - ) - - theta_comments <- comments[grepl("^THETA", names(comments))] - omega_comments <- comments[grepl("^OMEGA", names(comments))] - - info <- ModelComments(theta = theta_comments, omega = omega_comments) - - # All omega names should be unique after renaming - omega_names <- vapply( - info@omega, - function(c) c@name, - character(1) - ) - expect_equal( - length(omega_names), - length(unique(omega_names)), - info = paste("Failed for:", mod_file) - ) - - # Audit should show "renamed from" for all omega names - audit <- audit_parameter_info(info) - expect_true( - all(grepl("renamed from", audit$omega$name)), - info = paste("Failed for:", mod_file) - ) - } -}) diff --git a/tests/testthat/test-comments-query-edge.R b/tests/testthat/test-comments-query-edge.R index 09b89177..c3d61f11 100644 --- a/tests/testthat/test-comments-query-edge.R +++ b/tests/testthat/test-comments-query-edge.R @@ -3,16 +3,6 @@ test_that("get_comment returns NULL for non-parameter names", { expect_null(get_comment(info, "OTHER1")) }) -test_that("resolve_comment strips name suffixes", { - theta1 <- ThetaComment(nonmem_name = "THETA1", name = "CL") - info <- ModelComments(theta = list(THETA1 = theta1)) - - expect_equal( - get_parameter_transform(info, "THETA1 (CL)"), - "Identity" - ) -}) - test_that("get_theta_names rejects non-ModelComments input", { expect_error( get_theta_names(list()), diff --git a/tests/testthat/test-extract_raw_omega_parts.R b/tests/testthat/test-extract_raw_omega_parts.R deleted file mode 100644 index a896c97e..00000000 --- a/tests/testthat/test-extract_raw_omega_parts.R +++ /dev/null @@ -1,93 +0,0 @@ -test_that("extract_raw_omega_parts parses comments correctly", { - # Single word after prefix stripping - no theta ref, just name - parts <- extract_raw_omega_parts("OMEGA1: CL :EXP") - expect_equal(parts$name, "CL") - expect_equal(parts$parameterization, "EXP") - - parts <- extract_raw_omega_parts("1: CL :EXP") - expect_equal(parts$name, "CL") - expect_equal(parts$parameterization, "EXP") - - # Name is prefix only, associated_theta stored separately - parts <- extract_raw_omega_parts("1: OM2,1 CL-VC ; normal") - expect_equal(parts$name, "OM2,1") - expect_equal(parts$parameterization, "normal") - expect_equal(parts$associated_theta, c("CL", "VC")) - - parts <- extract_raw_omega_parts("1: OM2,1 CL/VC ; normal") - expect_equal(parts$name, "OM2,1") - expect_equal(parts$parameterization, "normal") - expect_equal(parts$associated_theta, c("CL", "VC")) - - parts <- extract_raw_omega_parts("OM2,1 CL,VC ; normal") - expect_equal(parts$name, "OM2,1") - expect_equal(parts$parameterization, "normal") - expect_equal(parts$associated_theta, c("CL", "VC")) - - # Single word - no theta ref - parts <- extract_raw_omega_parts("OMEGA1: CL ; exp") - expect_equal(parts$name, "CL") - expect_equal(parts$parameterization, "exp") - - # prefix + theta ref = prefix in name, theta in associated_theta - parts <- extract_raw_omega_parts("eta1 CL ; exp") - expect_equal(parts$name, "eta1") - expect_equal(parts$parameterization, "exp") - expect_equal(parts$associated_theta, "CL") - - parts <- extract_raw_omega_parts( - "OMEGA(2,1) CL/F-V2/F", - known_thetas = c("CL/F", "V2/F") - ) - expect_equal(parts$name, NULL) - expect_equal(parts$associated_theta, c("CL/F", "V2/F")) - expect_equal(parts$parameterization, NULL) - - parts <- extract_raw_omega_parts( - "OMEGA(2,1) Cov CL/F-V2/F", - known_thetas = c("CL/F", "V2/F") - ) - expect_equal(parts$name, "Cov") - expect_equal(parts$associated_theta, c("CL/F", "V2/F")) - expect_equal(parts$parameterization, NULL) - - parts <- extract_raw_omega_parts( - "CL/F-V2/F", - known_thetas = c("CL/F", "V2/F") - ) - expect_equal(parts$name, NULL) - expect_equal(parts$associated_theta, c("CL/F", "V2/F")) - expect_equal(parts$parameterization, NULL) - - parts <- extract_raw_omega_parts( - "CL/F:V2/F", - known_thetas = c("CL/F", "V2/F") - ) - expect_equal(parts$name, NULL) - expect_equal(parts$associated_theta, c("CL/F", "V2/F")) - expect_equal(parts$parameterization, NULL) -}) - -test_that("parse_raw_omega_comment handles off-diagonal pair and diagonal hyphenated name", { - known_thetas <- c("CL/F", "V2/F") - - # Off-diagonal covariance-style comment - offdiag <- parse_raw_omega_comment( - "OMEGA(2,1)", - NULL, - "Cov CL/F-V2/F", - known_thetas = known_thetas - ) - expect_equal(offdiag@name, "Cov") - expect_equal(offdiag@associated_theta, c("CL/F", "V2/F")) - - # Diagonal standard comment should remain name + single theta - diag <- parse_raw_omega_comment( - "OMEGA(1,1)", - NULL, - "IIV-CL/F", - known_thetas = known_thetas - ) - expect_equal(diag@name, "IIV") - expect_equal(diag@associated_theta, "CL/F") -}) diff --git a/tests/testthat/test-extract_raw_sigma_parts.R b/tests/testthat/test-extract_raw_sigma_parts.R deleted file mode 100644 index 4b6f4a86..00000000 --- a/tests/testthat/test-extract_raw_sigma_parts.R +++ /dev/null @@ -1,61 +0,0 @@ -test_that("extract_raw_sigma_parts accepts numbered prefixes", { - parts <- extract_raw_sigma_parts("1: Proportional error") - expect_equal(parts$name, "Proportional") - expect_equal(parts$parameterization, NULL) -}) - -test_that("extract_raw_sigma_parts handles numbered name comments", { - parts <- extract_raw_sigma_parts("11 PropErr ;Proportional") - expect_equal(parts$name, "PropErr") - expect_equal(parts$parameterization, "Proportional") -}) - -test_that("extract_raw_sigma_parts handles numbered name comments", { - parts <- extract_raw_sigma_parts("11 PropErr :Proportional") - expect_equal(parts$name, "PropErr") - expect_equal(parts$parameterization, "Proportional") -}) - -test_that("extract_raw_sigma_parts handles numbered name comments", { - parts <- extract_raw_sigma_parts("SIGMA2 AddErr :AddErr") - expect_equal(parts$name, "AddErr") - expect_equal(parts$parameterization, "AddErr") -}) - -test_that("extract_raw_sigma_parts captures units in parentheses or brackets", { - parts <- extract_raw_sigma_parts("22 AddErr (CONC)") - expect_equal(parts$name, "AddErr") - expect_equal(parts$unit, "CONC") - expect_equal(parts$parameterization, NULL) - - parts <- extract_raw_sigma_parts("AddErr [ng/mL] :ADD") - expect_equal(parts$name, "AddErr") - expect_equal(parts$unit, "ng/mL") - expect_equal(parts$parameterization, "ADD") - - parts <- extract_raw_sigma_parts("AddErr ;AddErr (ng/mL)") - expect_equal(parts$name, "AddErr") - expect_equal(parts$unit, "ng/mL") - expect_equal(parts$parameterization, "AddErr") - - parts <- extract_raw_sigma_parts("11 PropErr :Proportional [prop]") - expect_equal(parts$name, "PropErr") - expect_equal(parts$unit, "prop") - expect_equal(parts$parameterization, "Proportional") - - parts <- extract_raw_sigma_parts("22 AddErr :AddErr [ng/mL]") - expect_equal(parts$name, "AddErr") - expect_equal(parts$unit, "ng/mL") - expect_equal(parts$parameterization, "AddErr") - - parts <- extract_raw_sigma_parts("11 PropErr ;Proportional [prop]") - expect_equal(parts$name, "PropErr") - expect_equal(parts$unit, "prop") - expect_equal(parts$parameterization, "Proportional") - - parts <- extract_raw_sigma_parts("22 AddErr ;AddErr [ng/mL]") - expect_equal(parts$name, "AddErr") - expect_equal(parts$unit, "ng/mL") - expect_equal(parts$parameterization, "AddErr") - -}) diff --git a/tests/testthat/test-extract_raw_theta_parts.R b/tests/testthat/test-extract_raw_theta_parts.R deleted file mode 100644 index 48323c36..00000000 --- a/tests/testthat/test-extract_raw_theta_parts.R +++ /dev/null @@ -1,62 +0,0 @@ -test_that("extract_raw_theta_parts parses comments correctly", { - parts <- extract_raw_theta_parts("THETA1: CL (L/day) ; exp") - expect_equal(parts$name, "CL") - expect_equal(parts$unit, "L/day") - expect_equal(parts$parameterization, "exp") - - parts <- extract_raw_theta_parts("1: CL (L/day) ; exp") - expect_equal(parts$name, "CL") - expect_equal(parts$unit, "L/day") - expect_equal(parts$parameterization, "exp") - - parts <- extract_raw_theta_parts("1 CL (L/day) ; exp") - expect_equal(parts$name, "CL") - expect_equal(parts$unit, "L/day") - expect_equal(parts$parameterization, "exp") - - parts <- extract_raw_theta_parts("CL (L/day) ; exp") - expect_equal(parts$name, "CL") - expect_equal(parts$unit, "L/day") - expect_equal(parts$parameterization, "exp") - - parts <- extract_raw_theta_parts("CL ;exp") - expect_equal(parts$name, "CL") - expect_equal(parts$unit, NULL) - expect_equal(parts$parameterization, "exp") - - parts <- extract_raw_theta_parts("THETA1: CL (L/day) :EXP") - expect_equal(parts$name, "CL") - expect_equal(parts$unit, "L/day") - expect_equal(parts$parameterization, "EXP") - - parts <- extract_raw_theta_parts("THETA1 CL (L/day) :LOG") - expect_equal(parts$name, "CL") - expect_equal(parts$unit, "L/day") - expect_equal(parts$parameterization, "LOG") - - parts <- extract_raw_theta_parts("THETA6 RUV :ADD") - expect_equal(parts$name, "RUV") - expect_equal(parts$unit, NULL) - expect_equal(parts$parameterization, "ADD") - - parts <- extract_raw_theta_parts("THETA1 CL [L/day] :LOG") - expect_equal(parts$name, "CL") - expect_equal(parts$unit, "L/day") - expect_equal(parts$parameterization, "LOG") - - parts <- extract_raw_theta_parts(" 5 kon (1/(mg*hr))") - expect_equal(parts$name, "kon") - expect_equal(parts$unit, "1/(mg*hr)") - - parts <- extract_raw_theta_parts(" 5 kon [1/(mg*hr)]") - expect_equal(parts$name, "kon") - expect_equal(parts$unit, "1/(mg*hr)") - - parts <- extract_raw_theta_parts(" 5 kon (1/[mg*hr])") - expect_equal(parts$name, "kon") - expect_equal(parts$unit, "1/[mg*hr]") - - parts <- extract_raw_theta_parts(" 5 kon [1/[mg*hr]]") - expect_equal(parts$name, "kon") - expect_equal(parts$unit, "1/[mg*hr]") -}) diff --git a/tests/testthat/test-format-omega-display-name.R b/tests/testthat/test-format-omega-display-name.R deleted file mode 100644 index bda69aed..00000000 --- a/tests/testthat/test-format-omega-display-name.R +++ /dev/null @@ -1,125 +0,0 @@ -test_that("format_omega_display_name avoids duplicate theta info", { - # Theta already in name via hyphen - no duplication - expect_equal( - format_omega_display_name("IIV-CL", "CL"), - "IIV-CL" - ) - - # Theta already in name via slash - no duplication - expect_equal( - format_omega_display_name("IIV-CL/F", "CL/F"), - "IIV-CL/F" - ) - - # Theta already in name via slash - no duplication - expect_equal( - format_omega_display_name("IIV-CL", "CL/F"), - "IIV-CL" - ) - - # Theta not in name - appends it - expect_equal( - format_omega_display_name("IIV", "CL"), - "IIV CL" - ) - - # Multiple thetas, none present - expect_equal( - format_omega_display_name("COV", c("CL", "V")), - "COV CL, V" - ) - - # Multiple thetas, some present - expect_equal( - format_omega_display_name("IIV-CL", c("CL", "V")), - "IIV-CL V" - ) - - # With custom labels - label already present - expect_equal( - format_omega_display_name("IIV-Clearance", "CL", c(CL = "Clearance")), - "IIV-Clearance" - ) - - # With custom labels that include spaces - label already present - expect_equal( - format_omega_display_name( - "IIV CL/F Scaling", - "CLF", - c("CLF" = "CL/F Scaling") - ), - "IIV CL/F Scaling" - ) - - # With custom labels - appends label not name - expect_equal( - format_omega_display_name("IIV", "CL", c(CL = "Clearance")), - "IIV Clearance" - ) - - # NULL associated_theta returns name unchanged - expect_equal( - format_omega_display_name("IIV", NULL), - "IIV" - ) - - # Empty associated_theta returns name unchanged - expect_equal( - format_omega_display_name("IIV", character(0)), - "IIV" - ) -}) - -test_that("format_omega_display_name matches theta roots after stripping TV/ETA prefix", { - # CL in name matches TVCL theta (TVCL -> CL) - expect_equal( - format_omega_display_name("IIV-CL", "TVCL"), - "IIV-CL" - ) - - # Vc in name matches TVVC theta (TVVC -> VC, case-insensitive) - expect_equal( - format_omega_display_name("IIV-Vc", "TVVC"), - "IIV-Vc" - ) - - # KA in name matches TVKA theta (TVKA -> KA) - expect_equal( - format_omega_display_name("IIV-KA", "TVKA"), - "IIV-KA" - ) - - # Multiple TV-prefixed thetas - expect_equal( - format_omega_display_name("COV-CL-V", c("TVCL", "TVV")), - "COV-CL-V" - ) - - # Partial match - only CL present, V missing (TVV -> V, not in name) - expect_equal( - format_omega_display_name("IIV-CL", c("TVCL", "TVV")), - "IIV-CL TVV" - ) - - # ETA prefix also stripped - expect_equal( - format_omega_display_name("IIV-CL", "ETACL"), - "IIV-CL" - ) -}) - - -test_that("renaming work for off-diags", { - model_dir <- system.file("extdata", "models", "onecmt", package = "hyperion") - - mod <- read_model(file.path(model_dir, "run003.mod")) - info <- get_model_parameter_info(mod) - - display_name <- format_omega_display_name( - info@omega$`OMEGA(2,1)`@name, - info@omega$`OMEGA(2,1)`@associated_theta - ) - expect_equal(display_name, "OM1,2 TVCL, TVV") - expect_equal(info@omega$`OMEGA(2,1)`@name, "OM1,2") - expect_equal(info@omega$`OMEGA(2,1)`@associated_theta, c("TVCL", "TVV")) -}) diff --git a/tests/testthat/test-get_model_parameter_names.R b/tests/testthat/test-get_model_parameter_names.R deleted file mode 100644 index fd3ae3f5..00000000 --- a/tests/testthat/test-get_model_parameter_names.R +++ /dev/null @@ -1,44 +0,0 @@ -test_that("get_model_parameter_names works for typed comments", { - mod_dir <- system.file("extdata", "models", "onecmt", package = "hyperion") - - # run1 has incorrect type1 comments so nothing is parsed - run1 <- read_model(file.path(mod_dir, "run001.mod")) - expect_equal(get_comment_type(), "type1") - - n1 <- get_model_parameter_names(run1) - expect_equal(all(n1 == ""), TRUE) - - # run2 has correct type1 comments for THETA/OMEGA - # so parameter name should have non-empty values. - # Sigma is incorrect and will be empty - run2 <- read_model(file.path(mod_dir, "run002.mod")) - n2 <- get_model_parameter_names(run2) - expect_equal(any(n2 != ""), TRUE) - expect_equal(n2$THETA1, "TVCL") - expect_equal(n2$THETA2, "TVV") - expect_equal(n2$`SIGMA(1,1)`, "") - expect_equal(n2$`SIGMA(2,2)`, "") -}) - -test_that("get_parameter_names works for all comments", { - mod_dir <- system.file("extdata", "models", "onecmt", package = "hyperion") - - # run1 has incorrect type1 comments so nothing is parsed - run1 <- read_model(file.path(mod_dir, "run001.mod")) - n1 <- get_parameter_names(run1) - expect_equal(n1["THETA1", "name"], "TVCL") - expect_equal(n1["THETA2", "name"], "TVV") - - # run2 has correct type1 comments for THETA/OMEGA - # so parameter name should have non-empty values. - # Sigma is incorrect and will be empty, OMEGA is - # processed differently ((theta) vs , theta) - run2 <- read_model(file.path(mod_dir, "run002.mod")) - n2 <- get_parameter_names(run2) - n2_mp <- get_model_parameter_names(run2) - for (p in rownames(n2)) { - if (grepl("^THETA", p)) { - expect_equal(n2[p, "name"], n2_mp[[p]]) - } - } -}) diff --git a/tests/testthat/test-hyperion-summary-knit.R b/tests/testthat/test-hyperion-summary-knit.R index 04c0afb4..e3e47ce2 100644 --- a/tests/testthat/test-hyperion-summary-knit.R +++ b/tests/testthat/test-hyperion-summary-knit.R @@ -33,18 +33,3 @@ test_that("hyperion.nonmem-summary knit_print works for run004 (running)", { }) -test_that("hyperion.nonmem-summary knit_print fails gracefully for ill-formatted comments", { - # Temporarily remove type1 so raw comment parsing is exercised - config_path <- testthat::test_path("..", "pharos.toml") - original <- readLines(config_path) - withr::defer(writeLines(original, config_path)) - writeLines(gsub('type = "type1"', '', original), config_path) - - mod_path <- system.file("extdata", "models", "run-error-names", "run-err.mod", package = "hyperion") - mod <- read_model(mod_path) - expect_warning( - mod_sum <- summary(mod), - "Could not apply parameter names from model comments." - ) - snapshot_knit_html(mod_sum, "summary-knit-run-err") -}) diff --git a/tests/testthat/test-hyperion-summary-print.R b/tests/testthat/test-hyperion-summary-print.R index ae7866df..608e8693 100644 --- a/tests/testthat/test-hyperion-summary-print.R +++ b/tests/testthat/test-hyperion-summary-print.R @@ -31,19 +31,3 @@ test_that("hyperion.nonmem-summary print works for run004 (running)", { expect_snapshot(print(mod_sum)) }) -test_that("hyperion.nonmem-summary print fails gracefully for ill-formatted comments", { - # Temporarily remove type1 so raw comment parsing is exercised - config_path <- testthat::test_path("..", "pharos.toml") - original <- readLines(config_path) - withr::defer(writeLines(original, config_path)) - writeLines(gsub('type = "type1"', '', original), config_path) - - mod_path <- system.file("extdata", "models", "run-error-names", "run-err.mod", package = "hyperion") - mod <- read_model(mod_path) - - expect_warning( - mod_sum <- summary(mod), - "Could not apply parameter names from model comments." - ) - expect_snapshot(print(mod_sum)) -}) diff --git a/tests/testthat/test-name-lookup-omega.R b/tests/testthat/test-name-lookup-omega.R deleted file mode 100644 index ab55ed86..00000000 --- a/tests/testthat/test-name-lookup-omega.R +++ /dev/null @@ -1,27 +0,0 @@ -test_that("format_omega_display_name does not duplicate associated theta names", { - # Theta partially in name - only appends missing theta - expect_equal( - format_omega_display_name("Corr-CL", c("CL", "V")), - "Corr-CL V" - ) - - # Both thetas already in name - no duplication - expect_equal( - format_omega_display_name("Corr-CL-V", c("CL", "V")), - "Corr-CL-V" - ) -}) - -test_that("format_omega_display_name appends associated theta when not present", { - # Single theta not in name - expect_equal( - format_omega_display_name("IIV", "TVCL"), - "IIV TVCL" - ) - - # Multiple thetas not in name - expect_equal( - format_omega_display_name("IIV", c("TVCL", "TVV")), - "IIV TVCL, TVV" - ) -}) diff --git a/tests/testthat/test-parse_raw_omega_comment.R b/tests/testthat/test-parse_raw_omega_comment.R deleted file mode 100644 index 15c0cefd..00000000 --- a/tests/testthat/test-parse_raw_omega_comment.R +++ /dev/null @@ -1,86 +0,0 @@ -test_that("parse omega comments extracts name and associated_theta separately", { - # Name is prefix only, associated_theta stored separately - om_comment <- parse_raw_omega_comment("OMEGA(2,1)", NULL, "OM2,1 CL-VC") - expect_equal(om_comment@nonmem_name, "OMEGA(2,1)") - expect_equal(om_comment@name, "OM2,1") - expect_equal(om_comment@parameterization, NULL) - expect_equal(om_comment@associated_theta, c("CL", "VC")) - - om_comment <- parse_raw_omega_comment("OMEGA(2,1)", NULL, "OM2,1 CL-VC ;log") - expect_equal(om_comment@nonmem_name, "OMEGA(2,1)") - expect_equal(om_comment@name, "OM2,1") - expect_equal(om_comment@parameterization, "LogNormal") - expect_equal(om_comment@associated_theta, c("CL", "VC")) -}) - -test_that("omega name is prefix only, associated_theta stored separately", { - # Already hyphenated - extracts prefix and theta - result <- extract_raw_omega_parts("IIV-CL") - expect_equal(result$name, "IIV") - expect_equal(result$associated_theta, "CL") - - # Space between prefix and theta - result <- extract_raw_omega_parts("IIV CL") - expect_equal(result$name, "IIV") - expect_equal(result$associated_theta, "CL") - - # Linking word "on" - skipped - result <- extract_raw_omega_parts("IIV on CL") - expect_equal(result$name, "IIV") - expect_equal(result$associated_theta, "CL") - - # Different prefix - result <- extract_raw_omega_parts("OM1 CL") - expect_equal(result$name, "OM1") - expect_equal(result$associated_theta, "CL") - - # Another linking word - result <- extract_raw_omega_parts("eta on V") - expect_equal(result$name, "eta") - expect_equal(result$associated_theta, "V") - - # Correlation with hyphenated thetas - result <- extract_raw_omega_parts("Corr CL-V") - expect_equal(result$name, "Corr") - expect_equal(result$associated_theta, c("CL", "V")) -}) - -test_that("associated_theta splits unless matches known theta", { - # Without context - splits on separators - result <- extract_raw_omega_parts("IIV on CL/F") - expect_equal(result$name, "IIV") - expect_equal(result$associated_theta, c("CL", "F")) - - result <- extract_raw_omega_parts("Corr CL/V") - expect_equal(result$name, "Corr") - expect_equal(result$associated_theta, c("CL", "V")) - - # With known_thetas context - preserves known names - result <- extract_raw_omega_parts( - "IIV on CL/F", - known_thetas = c("CL/F", "V") - ) - expect_equal(result$name, "IIV") - expect_equal(result$associated_theta, "CL/F") - - # Case-insensitive match, preserve original case - result <- extract_raw_omega_parts("IIV on cl/f", known_thetas = c("CL/F")) - expect_equal(result$name, "IIV") - expect_equal(result$associated_theta, "cl/f") - - # With context but no match - still splits - result <- extract_raw_omega_parts("Corr CL/V", known_thetas = c("CL/F", "KA")) - expect_equal(result$name, "Corr") - expect_equal(result$associated_theta, c("CL", "V")) -}) - -test_that("linking words are skipped", { - result <- extract_raw_omega_parts("IIV on CL") - expect_equal(result$associated_theta, "CL") - - result <- extract_raw_omega_parts("IIV for V") - expect_equal(result$associated_theta, "V") - - result <- extract_raw_omega_parts("eta of KA") - expect_equal(result$associated_theta, "KA") -}) diff --git a/tests/testthat/test-parse_raw_theta_comment.R b/tests/testthat/test-parse_raw_theta_comment.R deleted file mode 100644 index f96b77b2..00000000 --- a/tests/testthat/test-parse_raw_theta_comment.R +++ /dev/null @@ -1,11 +0,0 @@ -test_that("parse theta comments works", { - th <- parse_raw_theta_comment("THETA1", NULL, "THETA1 RUV :ADD") - expect_equal(th@nonmem_name, "THETA1") - expect_equal(th@name, "RUV") - expect_equal(th@parameterization, "AddErr") - - th <- parse_raw_theta_comment("THETA2", NULL, "THETA2 CL :log") - expect_equal(th@nonmem_name, "THETA2") - expect_equal(th@name, "CL") - expect_equal(th@parameterization, "LogNormal") -}) diff --git a/tests/testthat/test-strip-param-prefix-parens.R b/tests/testthat/test-strip-param-prefix-parens.R deleted file mode 100644 index cfdf6cb9..00000000 --- a/tests/testthat/test-strip-param-prefix-parens.R +++ /dev/null @@ -1,9 +0,0 @@ -test_that("parameter prefixes with parentheses are stripped", { - theta_parts <- extract_raw_theta_parts("THETA(1): CL (L/h)") - expect_equal(theta_parts$name, "CL") - expect_equal(theta_parts$unit, "L/h") - - sigma_parts <- extract_raw_sigma_parts("SIGMA(1): PropErr ; prop") - expect_equal(sigma_parts$name, "PropErr") - expect_equal(sigma_parts$parameterization, "prop") -}) diff --git a/tests/testthat/test-strip-param-prefix.R b/tests/testthat/test-strip-param-prefix.R deleted file mode 100644 index 1f53a8ec..00000000 --- a/tests/testthat/test-strip-param-prefix.R +++ /dev/null @@ -1,9 +0,0 @@ -test_that("lowercase parameter prefixes are stripped", { - theta_parts <- extract_raw_theta_parts("theta1: CL (L/h)") - expect_equal(theta_parts$name, "CL") - expect_equal(theta_parts$unit, "L/h") - - omega_parts <- extract_raw_omega_parts("omega1: IIV CL ; exp") - expect_equal(omega_parts$name, "IIV") - expect_equal(omega_parts$associated_theta, "CL") -}) From c9e49cf45f248394ca115cfb90c2fcbc28ac0f91 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Mon, 20 Apr 2026 17:05:25 -0400 Subject: [PATCH 13/41] remove nmparser module moving model reading back to nonmem crate --- src/rust/Cargo.lock | 14 +- src/rust/Cargo.toml | 3 +- src/rust/lib.rs | 1 - src/rust/nmparser/Cargo.toml | 16 -- src/rust/nmparser/src/lib.rs | 192 ------------------------ src/rust/nonmem/Cargo.toml | 2 +- src/rust/nonmem/src/model/check.rs | 3 +- src/rust/nonmem/src/model/mod.rs | 89 ++++++++++- src/rust/nonmem/src/model/parameters.rs | 2 +- 9 files changed, 92 insertions(+), 230 deletions(-) delete mode 100644 src/rust/nmparser/Cargo.toml delete mode 100644 src/rust/nmparser/src/lib.rs diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 16db0b17..75b4b18b 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -463,7 +463,6 @@ dependencies = [ "extendr-api", "fs-err", "hyperion-core", - "hyperion-nmparser", "hyperion-nonmem", "hyperion-scheduler", "serde", @@ -479,17 +478,6 @@ dependencies = [ "extendr-api", ] -[[package]] -name = "hyperion-nmparser" -version = "0.1.0" -dependencies = [ - "config", - "extendr-api", - "fs-err", - "hyperion-core", - "nonmem-parser", -] - [[package]] name = "hyperion-nonmem" version = "0.1.0" @@ -498,9 +486,9 @@ dependencies = [ "extendr-api", "fs-err", "hyperion-core", - "hyperion-nmparser", "insta", "nonmem", + "nonmem-parser", "serde", "tempfile", ] diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index f4bd4767..0ff6f841 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -14,14 +14,13 @@ name = 'document' path = 'document.rs' [workspace] -members = ["nonmem", "scheduler", "core", "nmparser"] +members = ["nonmem", "scheduler", "core"] [dependencies] # Internal crates hyperion-nonmem = { path = "nonmem" } hyperion-scheduler = { path = "scheduler" } hyperion-core = { path = "core" } -hyperion-nmparser = { path = "nmparser" } # R Integration extendr-api = { workspace = true } diff --git a/src/rust/lib.rs b/src/rust/lib.rs index 6ab22696..304d57a5 100644 --- a/src/rust/lib.rs +++ b/src/rust/lib.rs @@ -11,6 +11,5 @@ extendr_module! { use init; use hyperion_core; use hyperion_nonmem; - use hyperion_nmparser; use hyperion_scheduler; } diff --git a/src/rust/nmparser/Cargo.toml b/src/rust/nmparser/Cargo.toml deleted file mode 100644 index b5d5f1e4..00000000 --- a/src/rust/nmparser/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "hyperion-nmparser" -version = "0.1.0" -edition = "2024" - -[dependencies] -# R Integration (needed for extendr macros in modules) -extendr-api = { workspace = true } -hyperion-core = { path = "../core" } - -# Pharos components -nmparser = {workspace = true} -config = { workspace = true } - -# Core utilities -fs-err = { workspace = true } diff --git a/src/rust/nmparser/src/lib.rs b/src/rust/nmparser/src/lib.rs deleted file mode 100644 index 6f30a0f3..00000000 --- a/src/rust/nmparser/src/lib.rs +++ /dev/null @@ -1,192 +0,0 @@ -use extendr_api::Result; -use extendr_api::deserializer::from_robj; -use extendr_api::prelude::*; -use extendr_api::serializer::to_robj; - -use fs_err as fs; -use std::path::Component; -use std::path::Path; -use std::path::PathBuf; - -use hyperion_core::find_config_dir; -use hyperion_core::{OptionExt, ResultExt, extendr_err}; -use nmparser::Model; - -/// Get comment_type from pharos.toml config, if configured. -fn get_comment_type() -> Option { - find_config_dir() - .ok() - .flatten() - .map(|dir| dir.join("pharos.toml")) - .and_then(|p| config::Config::load(p).ok()) - .and_then(|c| c.nonmem.as_ref().and_then(|n| n.comments.r#type)) -} - -/// Helper to convert Model to Robj for read_model and other model readers. -pub fn model_to_robj(model: &mut Model, path: impl AsRef) -> Result { - let path = path.as_ref(); - - // Populate parsed_comment fields before serialization - if let Some(ct) = get_comment_type() { - model.parse_comments(ct); - } - - let mut model_robj = to_robj(model).map_to_extendr_err("failed to create Robj from Model")?; - - add_filename_attr(&mut model_robj, path)?; - add_model_source_attr(&mut model_robj, path)?; - - set_model_class(&mut model_robj) -} - -fn add_filename_attr(model_robj: &mut Robj, path: &Path) -> Result<()> { - if let Some(n) = path.file_stem().and_then(|name| name.to_str()) { - model_robj - .set_attrib("filename", n.into_robj()) - .map_to_extendr_err("Failed to set filename attribute")?; - } - Ok(()) -} - -fn add_model_source_attr(model_robj: &mut Robj, path: &Path) -> Result<()> { - let source_path = to_config_relative(path)?; - model_robj - .set_attrib("model_source", source_path.into_robj()) - .map_to_extendr_err("Failed to set model source attribute")?; - - Ok(()) -} - -fn set_model_class(model_robj: &mut Robj) -> Result { - let result = model_robj - .set_class(["hyperion_nonmem_model"]) - .map_to_extendr_err("Failed to set class")?; - - Ok(result.to_owned()) -} - -/// Helper function to reconstruct a parser Model from hyperion_nonmem_model Robj -pub fn robj_to_model(model: &Robj) -> Result { - from_robj(model).map_to_extendr_err("Failed to create Model from Robj") -} - -fn validate_model_path(input_path: impl AsRef) -> Result { - let path = input_path.as_ref(); - - if path.is_dir() { - return Err(extendr_err!( - "Expected .mod or .ctl file path, got directory: {}", - path.display() - )); - } - - let ext = match path.extension().and_then(|e| e.to_str()) { - Some("mod") => "mod", - Some("ctl") => "ctl", - _ => { - return Err(extendr_err!( - "Expected .mod or .ctl file path: {}", - path.display() - )); - } - }; - - if path.exists() { - let stem = path - .file_stem() - .ok_or_extendr_err("Could not determine model file stem")? - .to_string_lossy() - .to_string(); - if let Some(parent) = path.parent() - && parent - .file_name() - .and_then(|name| name.to_str()) - .is_some_and(|name| name == stem.as_str()) - { - let candidate = parent.with_extension(ext); - return Err(extendr_err!( - "Expected input model file, got output model file: {}\n\ - Try: {}", - path.display(), - candidate.display() - )); - } - - return Ok(path.to_path_buf()); - } - - Err(extendr_err!("File not found: {}", path.display())) -} - -fn make_relative_path(base: &Path, target: &Path) -> PathBuf { - let base_components: Vec> = base.components().collect(); - let target_components: Vec> = target.components().collect(); - - if base_components.first() != target_components.first() { - return target.to_path_buf(); - } - - let mut idx = 0; - let max = base_components.len().min(target_components.len()); - while idx < max && base_components[idx] == target_components[idx] { - idx += 1; - } - - let mut rel = PathBuf::new(); - for _ in idx..base_components.len() { - rel.push(".."); - } - for comp in target_components.iter().skip(idx) { - rel.push(comp.as_os_str()); - } - - rel -} - -fn to_config_relative(path: impl AsRef) -> Result { - let path = path.as_ref(); - let config_dir = find_config_dir().map_to_extendr_err("Failed to find config dir")?; - - if let Some(dir) = config_dir { - let rel = make_relative_path(&dir, path); - return Ok(rel.to_string_lossy().to_string()); - } - - Ok(path.to_string_lossy().to_string()) -} - -/// Gets model object -/// -/// @param path path to mod or ctl file. -/// -/// @return hyperion_nonmem_model S3 object with `model_source` attribute -/// @export -/// -/// @examples \dontrun{ -/// read_model("model/nonmem/run001.mod") -/// } -#[extendr] -pub fn read_model(path: &str) -> Result { - let mod_path = validate_model_path(path)?; - let content = fs::read_to_string(&mod_path).map_to_extendr_err("")?; - - let mut model = match Model::parse(&content) { - Ok(model) => model, - Err(diags) => { - let msg = diags - .iter() - .map(|d| d.render(mod_path.as_path(), &content)) - .collect::>() - .join("\n"); - return Err(extendr_err!("Failed to read model file:\n{msg}")); - } - }; - let robj_model = model_to_robj(&mut model, mod_path)?; - Ok(robj_model) -} - -// Generate extendr module for R integration -extendr_module! { - mod hyperion_nmparser; - fn read_model; -} diff --git a/src/rust/nonmem/Cargo.toml b/src/rust/nonmem/Cargo.toml index b3eca921..865101bd 100644 --- a/src/rust/nonmem/Cargo.toml +++ b/src/rust/nonmem/Cargo.toml @@ -7,10 +7,10 @@ edition = "2024" # R Integration (needed for extendr macros in modules) extendr-api = { workspace = true } hyperion-core = { path = "../core" } -hyperion-nmparser = { path = "../nmparser" } # Pharos components nonmem = { workspace = true } +nmparser = { workspace = true } config = { workspace = true } # Core utilities diff --git a/src/rust/nonmem/src/model/check.rs b/src/rust/nonmem/src/model/check.rs index ab851e06..8915e23e 100644 --- a/src/rust/nonmem/src/model/check.rs +++ b/src/rust/nonmem/src/model/check.rs @@ -4,9 +4,10 @@ use extendr_api::serializer::to_robj; use hyperion_core::{OptionExt, ResultExt}; // pharos config and nonmem crates -use hyperion_nmparser::robj_to_model; use nonmem::{check_dataset as nonmem_check_dataset, check_model}; +use crate::model::robj_to_model; + use crate::utils::{from_config_relative, load_nonmem_config, path_from_robj}; use hyperion_core::extendr_err; diff --git a/src/rust/nonmem/src/model/mod.rs b/src/rust/nonmem/src/model/mod.rs index 35bf0317..8fc8bc0e 100644 --- a/src/rust/nonmem/src/model/mod.rs +++ b/src/rust/nonmem/src/model/mod.rs @@ -1,12 +1,15 @@ use extendr_api::Result; +use extendr_api::deserializer::from_robj; use extendr_api::prelude::*; +use extendr_api::serializer::to_robj; +use fs_err as fs; +use nmparser::Model; use nonmem::output_files::lst; use std::path::Path; use crate::model::run_status::determine_run_status; -use crate::utils::find_output_file; -use hyperion_core::ResultExt; -use hyperion_nmparser::model_to_robj; +use crate::utils::{find_output_file, get_comment_type, to_config_relative, validate_model_path}; +use hyperion_core::{ResultExt, extendr_err}; pub mod check; pub mod copy; @@ -16,6 +19,53 @@ pub mod parameters; pub mod run_status; pub mod summary; +/// Convert a parsed Model into a hyperion_nonmem_model Robj. +pub fn model_to_robj(model: &mut Model, path: impl AsRef) -> Result { + let path = path.as_ref(); + + if let Some(ct) = get_comment_type() { + model.parse_comments(ct); + } + + let mut model_robj = to_robj(model).map_to_extendr_err("failed to create Robj from Model")?; + + add_filename_attr(&mut model_robj, path)?; + add_model_source_attr(&mut model_robj, path)?; + + set_model_class(&mut model_robj) +} + +/// Reconstruct a parser Model from a hyperion_nonmem_model Robj. +pub fn robj_to_model(model: &Robj) -> Result { + from_robj(model).map_to_extendr_err("Failed to create Model from Robj") +} + +fn add_filename_attr(model_robj: &mut Robj, path: &Path) -> Result<()> { + if let Some(n) = path.file_stem().and_then(|name| name.to_str()) { + model_robj + .set_attrib("filename", n.into_robj()) + .map_to_extendr_err("Failed to set filename attribute")?; + } + Ok(()) +} + +fn add_model_source_attr(model_robj: &mut Robj, path: &Path) -> Result<()> { + let source_path = to_config_relative(path)?; + model_robj + .set_attrib("model_source", source_path.into_robj()) + .map_to_extendr_err("Failed to set model source attribute")?; + + Ok(()) +} + +fn set_model_class(model_robj: &mut Robj) -> Result { + let result = model_robj + .set_class(["hyperion_nonmem_model"]) + .map_to_extendr_err("Failed to set class")?; + + Ok(result.to_owned()) +} + fn add_run_status_attr(model_robj: &mut Robj, path: &Path) -> Result<()> { if let Some(ext) = path.extension().and_then(|e| e.to_str()) && (ext == "mod" || ext == "ctl" || ext == "lst") @@ -45,6 +95,38 @@ pub fn read_model_from_lst(path: &str) -> Result { Ok(robj_model) } +/// Gets model object +/// +/// @param path path to mod or ctl file. +/// +/// @return hyperion_nonmem_model S3 object with `model_source` attribute +/// @export +/// +/// @examples \dontrun{ +/// read_model("model/nonmem/run001.mod") +/// } +#[extendr] +pub fn read_model(path: &str) -> Result { + let mod_path = validate_model_path(path)?; + let content = fs::read_to_string(&mod_path).map_to_extendr_err("")?; + + let mut model = match Model::parse(&content) { + Ok(model) => model, + Err(diags) => { + let msg = diags + .iter() + .map(|d| d.render(mod_path.as_path(), &content)) + .collect::>() + .join("\n"); + return Err(extendr_err!("Failed to read model file:\n{msg}")); + } + }; + let mut robj_model = model_to_robj(&mut model, &mod_path)?; + add_run_status_attr(&mut robj_model, &mod_path)?; + + Ok(robj_model) +} + extendr_module! { mod model; use copy; @@ -55,5 +137,6 @@ extendr_module! { use metadata; use run_status; + fn read_model; fn read_model_from_lst; } diff --git a/src/rust/nonmem/src/model/parameters.rs b/src/rust/nonmem/src/model/parameters.rs index 1653b0ce..854f144b 100644 --- a/src/rust/nonmem/src/model/parameters.rs +++ b/src/rust/nonmem/src/model/parameters.rs @@ -2,7 +2,6 @@ use extendr_api::Result; use extendr_api::prelude::*; use fs_err as fs; -use hyperion_nmparser::robj_to_model; use std::cmp::Ordering; use std::ffi::OsStr; use std::path::PathBuf; @@ -14,6 +13,7 @@ use nonmem::{ }; use crate::{ + model::robj_to_model, output_files::ext::create_ext_reader, output_files::{OMEGA, ParameterRow, ParameterRowBuilder, SIGMA, THETA, build_parameters_df}, utils::{find_output_file, get_comment_type, path_from_robj}, From b31ba792a6a3452cd1e7fbaa0e7113e20ff69241 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Mon, 20 Apr 2026 17:14:39 -0400 Subject: [PATCH 14/41] update docs --- R/extendr-wrappers.R | 36 +++++++++++++++++++------------- man/read_model.Rd | 13 ++++++++---- man/read_model_from_lst.Rd | 13 ++++++++---- src/rust/nonmem/src/model/mod.rs | 18 ++++++++++------ 4 files changed, 51 insertions(+), 29 deletions(-) diff --git a/R/extendr-wrappers.R b/R/extendr-wrappers.R index ef687333..3800a88c 100644 --- a/R/extendr-wrappers.R +++ b/R/extendr-wrappers.R @@ -22,11 +22,29 @@ set_panic_message <- function() .Call(wrap__set_panic_message) find_pharos_config_file <- function() .Call(wrap__find_pharos_config_file) -#' Gets model object from lst file (internal) +#' Read a NONMEM model from a .mod or .ctl file #' -#' @param path path to lst file, model output directory, or metadata.json file. +#' @param path path to a .mod or .ctl file. #' -#' @return hyperion_nonmem_model S3 object with `model_source` attribute for the source file +#' @return A `hyperion_nonmem_model` S3 object with attributes: +#' - `filename`: the model stem (e.g. `"run001"`) +#' - `model_source`: path to the source file, relative to the pharos config dir +#' - `run_status`: `"run"`, `"running"`, or `"not_run"` determined from output files on disk +#' @export +#' +#' @examples \dontrun{ +#' read_model("model/nonmem/run001.mod") +#' } +read_model <- function(path) .Call(wrap__read_model, path) + +#' Read a model from an .lst file (internal) +#' +#' @param path path to an .lst file, model output directory, or metadata.json file. +#' +#' @return A `hyperion_nonmem_model` S3 object with attributes: +#' - `filename`: the model stem (e.g. `"run001"`) +#' - `model_source`: path to the source file, relative to the pharos config dir +#' - `run_status`: `"run"`, `"running"`, or `"not_run"` determined from output files on disk #' @keywords internal read_model_from_lst <- function(path) .Call(wrap__read_model_from_lst, path) @@ -451,18 +469,6 @@ from_config_relative <- function(path) .Call(wrap__from_config_relative_wrap, pa #' @noRd to_config_relative <- function(path) .Call(wrap__to_config_relative_wrap, path) -#' Gets model object -#' -#' @param path path to mod or ctl file. -#' -#' @return hyperion_nonmem_model S3 object with `model_source` attribute -#' @export -#' -#' @examples \dontrun{ -#' read_model("model/nonmem/run001.mod") -#' } -read_model <- function(path) .Call(wrap__read_model, path) - #' Submits a NONMEM model to SLURM for execution #' #' This function submits a NONMEM model file to a SLURM cluster for execution, diff --git a/man/read_model.Rd b/man/read_model.Rd index 85cbec4d..973ffe22 100644 --- a/man/read_model.Rd +++ b/man/read_model.Rd @@ -2,18 +2,23 @@ % Please edit documentation in R/extendr-wrappers.R \name{read_model} \alias{read_model} -\title{Gets model object} +\title{Read a NONMEM model from a .mod or .ctl file} \usage{ read_model(path) } \arguments{ -\item{path}{path to mod or ctl file.} +\item{path}{path to a .mod or .ctl file.} } \value{ -hyperion_nonmem_model S3 object with \code{model_source} attribute +A \code{hyperion_nonmem_model} S3 object with attributes: +\itemize{ +\item \code{filename}: the model stem (e.g. \code{"run001"}) +\item \code{model_source}: path to the source file, relative to the pharos config dir +\item \code{run_status}: \code{"run"}, \code{"running"}, or \code{"not_run"} determined from output files on disk +} } \description{ -Gets model object +Read a NONMEM model from a .mod or .ctl file } \examples{ \dontrun{ diff --git a/man/read_model_from_lst.Rd b/man/read_model_from_lst.Rd index 9cb6c081..1dcdf133 100644 --- a/man/read_model_from_lst.Rd +++ b/man/read_model_from_lst.Rd @@ -2,17 +2,22 @@ % Please edit documentation in R/extendr-wrappers.R \name{read_model_from_lst} \alias{read_model_from_lst} -\title{Gets model object from lst file (internal)} +\title{Read a model from an .lst file (internal)} \usage{ read_model_from_lst(path) } \arguments{ -\item{path}{path to lst file, model output directory, or metadata.json file.} +\item{path}{path to an .lst file, model output directory, or metadata.json file.} } \value{ -hyperion_nonmem_model S3 object with \code{model_source} attribute for the source file +A \code{hyperion_nonmem_model} S3 object with attributes: +\itemize{ +\item \code{filename}: the model stem (e.g. \code{"run001"}) +\item \code{model_source}: path to the source file, relative to the pharos config dir +\item \code{run_status}: \code{"run"}, \code{"running"}, or \code{"not_run"} determined from output files on disk +} } \description{ -Gets model object from lst file (internal) +Read a model from an .lst file (internal) } \keyword{internal} diff --git a/src/rust/nonmem/src/model/mod.rs b/src/rust/nonmem/src/model/mod.rs index 8fc8bc0e..43f73f52 100644 --- a/src/rust/nonmem/src/model/mod.rs +++ b/src/rust/nonmem/src/model/mod.rs @@ -78,11 +78,14 @@ fn add_run_status_attr(model_robj: &mut Robj, path: &Path) -> Result<()> { Ok(()) } -/// Gets model object from lst file (internal) +/// Read a model from an .lst file (internal) /// -/// @param path path to lst file, model output directory, or metadata.json file. +/// @param path path to an .lst file, model output directory, or metadata.json file. /// -/// @return hyperion_nonmem_model S3 object with `model_source` attribute for the source file +/// @return A `hyperion_nonmem_model` S3 object with attributes: +/// - `filename`: the model stem (e.g. `"run001"`) +/// - `model_source`: path to the source file, relative to the pharos config dir +/// - `run_status`: `"run"`, `"running"`, or `"not_run"` determined from output files on disk /// @keywords internal #[extendr] pub fn read_model_from_lst(path: &str) -> Result { @@ -95,11 +98,14 @@ pub fn read_model_from_lst(path: &str) -> Result { Ok(robj_model) } -/// Gets model object +/// Read a NONMEM model from a .mod or .ctl file /// -/// @param path path to mod or ctl file. +/// @param path path to a .mod or .ctl file. /// -/// @return hyperion_nonmem_model S3 object with `model_source` attribute +/// @return A `hyperion_nonmem_model` S3 object with attributes: +/// - `filename`: the model stem (e.g. `"run001"`) +/// - `model_source`: path to the source file, relative to the pharos config dir +/// - `run_status`: `"run"`, `"running"`, or `"not_run"` determined from output files on disk /// @export /// /// @examples \dontrun{ From ea9bec69e00322222ef9ccd26e888e851ff2ddc0 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Tue, 21 Apr 2026 13:29:13 -0400 Subject: [PATCH 15/41] update for sim fix --- src/rust/Cargo.lock | 16 ++++++++-------- src/rust/Cargo.toml | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 75b4b18b..b431e00c 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -141,7 +141,7 @@ dependencies = [ [[package]] name = "config" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=parsed-comments#32529d44bbde2cc6545e3f84a821a3f3fb18079b" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#c4f0ab12cd97370a02b3e802f7719ac72638fec8" dependencies = [ "anyhow", "fs-err", @@ -711,7 +711,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "nonmem" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=parsed-comments#32529d44bbde2cc6545e3f84a821a3f3fb18079b" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#c4f0ab12cd97370a02b3e802f7719ac72638fec8" dependencies = [ "anyhow", "blake3", @@ -737,7 +737,7 @@ dependencies = [ [[package]] name = "nonmem-parser" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=parsed-comments#32529d44bbde2cc6545e3f84a821a3f3fb18079b" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#c4f0ab12cd97370a02b3e802f7719ac72638fec8" dependencies = [ "anyhow", "distrs", @@ -1089,7 +1089,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=parsed-comments#32529d44bbde2cc6545e3f84a821a3f3fb18079b" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#c4f0ab12cd97370a02b3e802f7719ac72638fec8" dependencies = [ "anyhow", "config", @@ -1293,7 +1293,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.1", + "winnow 1.0.2", ] [[package]] @@ -1335,7 +1335,7 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utils" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=parsed-comments#32529d44bbde2cc6545e3f84a821a3f3fb18079b" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#c4f0ab12cd97370a02b3e802f7719ac72638fec8" dependencies = [ "anyhow", "fs-err", @@ -1557,9 +1557,9 @@ checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" [[package]] name = "winnow" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" [[package]] name = "wit-bindgen" diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 0ff6f841..19f76858 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -38,10 +38,10 @@ serde = { workspace = true } extendr-api = { git = "https://github.com/extendr/extendr", branch = "main", features = ["serde"] } # Pharos components -nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "parsed-comments" } -nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", branch = "parsed-comments" } -config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "parsed-comments" } -scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "parsed-comments" } +nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "sim-record" } +nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", branch = "sim-record" } +config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "sim-record" } +scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "sim-record" } # Core utilities anyhow = "1.0.100" From 44d785511e09eef0ec173c4a83b341e1e37caebf Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Tue, 21 Apr 2026 15:09:40 -0400 Subject: [PATCH 16/41] update sim-fix --- src/rust/Cargo.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index b431e00c..e90fdd85 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -141,7 +141,7 @@ dependencies = [ [[package]] name = "config" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#c4f0ab12cd97370a02b3e802f7719ac72638fec8" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#e3f8de10a90dbb9e3fb5fae1c85f12a60cf49261" dependencies = [ "anyhow", "fs-err", @@ -711,7 +711,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "nonmem" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#c4f0ab12cd97370a02b3e802f7719ac72638fec8" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#e3f8de10a90dbb9e3fb5fae1c85f12a60cf49261" dependencies = [ "anyhow", "blake3", @@ -737,7 +737,7 @@ dependencies = [ [[package]] name = "nonmem-parser" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#c4f0ab12cd97370a02b3e802f7719ac72638fec8" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#e3f8de10a90dbb9e3fb5fae1c85f12a60cf49261" dependencies = [ "anyhow", "distrs", @@ -1089,7 +1089,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#c4f0ab12cd97370a02b3e802f7719ac72638fec8" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#e3f8de10a90dbb9e3fb5fae1c85f12a60cf49261" dependencies = [ "anyhow", "config", @@ -1335,7 +1335,7 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utils" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#c4f0ab12cd97370a02b3e802f7719ac72638fec8" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#e3f8de10a90dbb9e3fb5fae1c85f12a60cf49261" dependencies = [ "anyhow", "fs-err", From 6164afe7f3dd8f84fd929e5b3f2477e1b6cc742e Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Tue, 21 Apr 2026 22:06:48 -0400 Subject: [PATCH 17/41] update sim-fix --- src/rust/Cargo.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index e90fdd85..319cb230 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -141,7 +141,7 @@ dependencies = [ [[package]] name = "config" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#e3f8de10a90dbb9e3fb5fae1c85f12a60cf49261" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#767b2525a5c5ea428f2797d50b633ea59e0fa042" dependencies = [ "anyhow", "fs-err", @@ -711,7 +711,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "nonmem" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#e3f8de10a90dbb9e3fb5fae1c85f12a60cf49261" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#767b2525a5c5ea428f2797d50b633ea59e0fa042" dependencies = [ "anyhow", "blake3", @@ -737,7 +737,7 @@ dependencies = [ [[package]] name = "nonmem-parser" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#e3f8de10a90dbb9e3fb5fae1c85f12a60cf49261" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#767b2525a5c5ea428f2797d50b633ea59e0fa042" dependencies = [ "anyhow", "distrs", @@ -1089,7 +1089,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#e3f8de10a90dbb9e3fb5fae1c85f12a60cf49261" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#767b2525a5c5ea428f2797d50b633ea59e0fa042" dependencies = [ "anyhow", "config", @@ -1335,7 +1335,7 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utils" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#e3f8de10a90dbb9e3fb5fae1c85f12a60cf49261" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#767b2525a5c5ea428f2797d50b633ea59e0fa042" dependencies = [ "anyhow", "fs-err", From bc4b06e840a87f2b1afd667e390e264f35dc3472 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Tue, 21 Apr 2026 22:28:59 -0400 Subject: [PATCH 18/41] update sim-record --- src/rust/Cargo.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 319cb230..b299b821 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -141,7 +141,7 @@ dependencies = [ [[package]] name = "config" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#767b2525a5c5ea428f2797d50b633ea59e0fa042" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#582b910d02356bb8848e61a77f1bffb1fca6f0ae" dependencies = [ "anyhow", "fs-err", @@ -711,7 +711,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "nonmem" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#767b2525a5c5ea428f2797d50b633ea59e0fa042" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#582b910d02356bb8848e61a77f1bffb1fca6f0ae" dependencies = [ "anyhow", "blake3", @@ -737,7 +737,7 @@ dependencies = [ [[package]] name = "nonmem-parser" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#767b2525a5c5ea428f2797d50b633ea59e0fa042" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#582b910d02356bb8848e61a77f1bffb1fca6f0ae" dependencies = [ "anyhow", "distrs", @@ -1089,7 +1089,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#767b2525a5c5ea428f2797d50b633ea59e0fa042" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#582b910d02356bb8848e61a77f1bffb1fca6f0ae" dependencies = [ "anyhow", "config", @@ -1335,7 +1335,7 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utils" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#767b2525a5c5ea428f2797d50b633ea59e0fa042" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#582b910d02356bb8848e61a77f1bffb1fca6f0ae" dependencies = [ "anyhow", "fs-err", From ecb8fd8b3e59caeaf38e007c3c4616b57d11cccc Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Tue, 21 Apr 2026 22:48:24 -0400 Subject: [PATCH 19/41] update pharos --- src/rust/Cargo.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index b299b821..47ce7dbe 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -141,7 +141,7 @@ dependencies = [ [[package]] name = "config" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#582b910d02356bb8848e61a77f1bffb1fca6f0ae" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#ce3bae4dc7bf658df1946576b136f1f8fa8edba0" dependencies = [ "anyhow", "fs-err", @@ -711,7 +711,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "nonmem" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#582b910d02356bb8848e61a77f1bffb1fca6f0ae" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#ce3bae4dc7bf658df1946576b136f1f8fa8edba0" dependencies = [ "anyhow", "blake3", @@ -737,7 +737,7 @@ dependencies = [ [[package]] name = "nonmem-parser" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#582b910d02356bb8848e61a77f1bffb1fca6f0ae" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#ce3bae4dc7bf658df1946576b136f1f8fa8edba0" dependencies = [ "anyhow", "distrs", @@ -1089,7 +1089,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#582b910d02356bb8848e61a77f1bffb1fca6f0ae" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#ce3bae4dc7bf658df1946576b136f1f8fa8edba0" dependencies = [ "anyhow", "config", @@ -1335,7 +1335,7 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utils" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#582b910d02356bb8848e61a77f1bffb1fca6f0ae" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#ce3bae4dc7bf658df1946576b136f1f8fa8edba0" dependencies = [ "anyhow", "fs-err", From 745364dbaecfdaef2815efdb05bdfa4ed6a007ec Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Wed, 22 Apr 2026 00:06:37 -0400 Subject: [PATCH 20/41] update pharos --- src/rust/Cargo.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 47ce7dbe..d4ada466 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -141,7 +141,7 @@ dependencies = [ [[package]] name = "config" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#ce3bae4dc7bf658df1946576b136f1f8fa8edba0" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#25f9056382de006ca1e0e37f4bfd3aea309c5672" dependencies = [ "anyhow", "fs-err", @@ -711,7 +711,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "nonmem" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#ce3bae4dc7bf658df1946576b136f1f8fa8edba0" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#25f9056382de006ca1e0e37f4bfd3aea309c5672" dependencies = [ "anyhow", "blake3", @@ -737,7 +737,7 @@ dependencies = [ [[package]] name = "nonmem-parser" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#ce3bae4dc7bf658df1946576b136f1f8fa8edba0" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#25f9056382de006ca1e0e37f4bfd3aea309c5672" dependencies = [ "anyhow", "distrs", @@ -1089,7 +1089,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#ce3bae4dc7bf658df1946576b136f1f8fa8edba0" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#25f9056382de006ca1e0e37f4bfd3aea309c5672" dependencies = [ "anyhow", "config", @@ -1335,7 +1335,7 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utils" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#ce3bae4dc7bf658df1946576b136f1f8fa8edba0" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#25f9056382de006ca1e0e37f4bfd3aea309c5672" dependencies = [ "anyhow", "fs-err", From c35d905fc400126c498d196c376488f4e3756832 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Thu, 23 Apr 2026 14:20:58 -0400 Subject: [PATCH 21/41] update grd names + snapshot fixes --- inst/extdata/mod/everything.mod | 5 +-- inst/extdata/models/onecmt/run004.mod | 12 ++++--- src/rust/Cargo.lock | 18 +++++----- src/rust/nonmem/src/output_files/grd.rs | 27 +++++++++++++- .../model-knit-everything.html | 2 +- tests/testthat/_snaps/hyperion-model-print.md | 2 +- .../summary-knit-run004-running.html | 18 +++++----- .../summary-knit-run004.html | 18 +++++----- .../testthat/_snaps/hyperion-summary-print.md | 36 +++++++++---------- 9 files changed, 83 insertions(+), 55 deletions(-) diff --git a/inst/extdata/mod/everything.mod b/inst/extdata/mod/everything.mod index 0e6a3baa..96539e36 100644 --- a/inst/extdata/mod/everything.mod +++ b/inst/extdata/mod/everything.mod @@ -1,6 +1,6 @@ $PROBLEM Some header #2 $INPUT ID TIME DV DOSE=AMT DV WT AGE SEX CREA DATE=DROP -$DATA ..\data.csv IGNORE=# +$DATA "..\path with spaces\data.csv" IGNORE=# IGNORE=(DVID.EQ.3) IGN(ID.EQ.3.14) IGNORE=(DVID==3) @@ -94,4 +94,5 @@ $TABLE ID TIME AMT EVID IPRED AGE WT MDV ETAS(1:LAST) ONEHEADER NOPRINT FILE=../ $TABLE ID FILE=001.tab $TABLE ID TIME AMT EVID AGE WT MDV KA CL V2 V3 Q BETA HLBE ONEHEADER NOPRINT FILE=../2par.TAB -$SIM ONLYSIM +$MSFI msfb.msf +$SIM (1) (2 NONPARAMETRIC) SUBPROBLEMS=1 diff --git a/inst/extdata/models/onecmt/run004.mod b/inst/extdata/models/onecmt/run004.mod index 6ea48660..37bce9d6 100644 --- a/inst/extdata/models/onecmt/run004.mod +++ b/inst/extdata/models/onecmt/run004.mod @@ -30,14 +30,16 @@ $THETA (0, 41.98) ;TVV (L) (0, 1.24) ;TVKA (1/hr) +$OMEGA BLOCK(2) +0.1 ;OM1 TVCL :EXP +0.0001 ;OM1,2 TVCL:TVV :EXP +0.1 ;OM2 TVV :EXP $OMEGA -0.126 ;OM1 TVCL -0.133 ;OM2 TVV -0.1 FIX ;OM3 TVKA +0.1 ;OM3 TVKA :EXP $SIGMA -0.0364 ; 1. Proportional error (variance, 20% CV) -0.01 FIX ; 2. Additive error (variance, 0.01 mg/L SD) +0.035738 ;SIG1 Proportional error (variance, 20% CV) +0.006 ;SIG2 Additive error (variance, 0.01 mg/L SD) $ESTIMATION METHOD=1 INTERACTION MAXEVAL=9999 PRINT=5 $TABLE ID TIME DV PRED IPRED CWRES NPDE NOAPPEND NOPRINT ONEHEADER FILE=run004.tab diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index d4ada466..1e1ab51e 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -141,7 +141,7 @@ dependencies = [ [[package]] name = "config" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#25f9056382de006ca1e0e37f4bfd3aea309c5672" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#ffadc71d3e20ae4c14d35f9b337960ebd703f179" dependencies = [ "anyhow", "fs-err", @@ -585,9 +585,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" dependencies = [ "jiff-static", "jiff-tzdb-platform", @@ -600,9 +600,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" dependencies = [ "proc-macro2", "quote", @@ -711,7 +711,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "nonmem" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#25f9056382de006ca1e0e37f4bfd3aea309c5672" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#ffadc71d3e20ae4c14d35f9b337960ebd703f179" dependencies = [ "anyhow", "blake3", @@ -737,7 +737,7 @@ dependencies = [ [[package]] name = "nonmem-parser" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#25f9056382de006ca1e0e37f4bfd3aea309c5672" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#ffadc71d3e20ae4c14d35f9b337960ebd703f179" dependencies = [ "anyhow", "distrs", @@ -1089,7 +1089,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#25f9056382de006ca1e0e37f4bfd3aea309c5672" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#ffadc71d3e20ae4c14d35f9b337960ebd703f179" dependencies = [ "anyhow", "config", @@ -1335,7 +1335,7 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utils" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#25f9056382de006ca1e0e37f4bfd3aea309c5672" +source = "git+https://github.com/a2-ai/pharos?branch=sim-record#ffadc71d3e20ae4c14d35f9b337960ebd703f179" dependencies = [ "anyhow", "fs-err", diff --git a/src/rust/nonmem/src/output_files/grd.rs b/src/rust/nonmem/src/output_files/grd.rs index 35d47cee..6528e211 100644 --- a/src/rust/nonmem/src/output_files/grd.rs +++ b/src/rust/nonmem/src/output_files/grd.rs @@ -7,6 +7,26 @@ use nonmem::{estimation::EstimationMethod, output_files::grd::GrdReader}; use crate::utils::{find_output_file, get_comment_type, path_from_robj, try_parse_model}; use hyperion_core::{ResultExt, extendr_err}; +/// Turn a pharos-produced gradient column name like `"GRD(IIV (CL))"` into a +/// syntactic R column name like `"IIV_CL"`. +fn sanitize_grd_name(raw: &str) -> String { + let inner = raw + .strip_prefix("GRD(") + .and_then(|s| s.strip_suffix(')')) + .unwrap_or(raw); + + let mut out: String = inner + .chars() + .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' }) + .collect(); + + while out.contains("__") { + out = out.replace("__", "_"); + } + + out.trim_matches('_').to_string() +} + fn create_grd_reader(only_method: Option<&str>, only_last: Option) -> Result { let mut reader = GrdReader::default(); @@ -72,7 +92,12 @@ pub fn get_gradients( } // Get gradient parameter names from the first table (skip ITERATION column) - let gradient_names: Vec = tables[0].parameters.iter().skip(1).cloned().collect(); + let gradient_names: Vec = tables[0] + .parameters + .iter() + .skip(1) + .map(|n| sanitize_grd_name(n)) + .collect(); let flat_data: Vec<(i32, String, Vec)> = tables .into_iter() diff --git a/tests/testthat/_snaps/hyperion-model-knit/model-knit-everything.html b/tests/testthat/_snaps/hyperion-model-knit/model-knit-everything.html index 3d50a1e5..72501bca 100644 --- a/tests/testthat/_snaps/hyperion-model-knit/model-knit-everything.html +++ b/tests/testthat/_snaps/hyperion-model-knit/model-knit-everything.html @@ -5,7 +5,7 @@ Run Status: Not Run -Dataset: ../data.csv +Dataset: ../path with spaces/data.csv Ignore: #, DVID.EQ.3, ID.EQ.3.14, DVID.EQ.3, AGE.GE.18, AGE.GT.3, AGE.LT.100, AGE.LE.65, TYPE.NE.0, TYPE.EQ.1, TYPE.EQN.1, TYPE.NEN.2, TYPE.EQ.1 diff --git a/tests/testthat/_snaps/hyperion-model-print.md b/tests/testthat/_snaps/hyperion-model-print.md index 20ce6f2a..dcd3109b 100644 --- a/tests/testthat/_snaps/hyperion-model-print.md +++ b/tests/testthat/_snaps/hyperion-model-print.md @@ -57,7 +57,7 @@ -- NONMEM Model: everything ---------------------------------------------------- Problem: Some header #2 Run Status: Not Run - Dataset: ..\data.csv + Dataset: ..\path with spaces\data.csv Ignore: #, DVID.EQ.3, ID.EQ.3.14, DVID.EQ.3, AGE.GE.18, AGE.GT.3, AGE.LT.100, AGE.LE.65, TYPE.NE.0, TYPE.EQ.1, TYPE.EQN.1, TYPE.NEN.2, TYPE.EQ.1 Records: 200 diff --git a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004-running.html b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004-running.html index 6154aa8f..8027a1c9 100644 --- a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004-running.html +++ b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004-running.html @@ -149,15 +149,15 @@ iteration method - GRD.TVCL. - GRD.TVV. - GRD.TVKA. - GRD.OM1..TVCL.. - GRD.OM2..TVV.. - GRD.EPS1. - GRD.7. - GRD.8. - GRD.9. + TVCL + TVV + TVKA + OM1_TVCL + OM1_2_TVCL_TVV + OM2_TVV + OM3_TVKA + EPS1 + EPS2 diff --git a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004.html b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004.html index 6154aa8f..8027a1c9 100644 --- a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004.html +++ b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004.html @@ -149,15 +149,15 @@ iteration method - GRD.TVCL. - GRD.TVV. - GRD.TVKA. - GRD.OM1..TVCL.. - GRD.OM2..TVV.. - GRD.EPS1. - GRD.7. - GRD.8. - GRD.9. + TVCL + TVV + TVKA + OM1_TVCL + OM1_2_TVCL_TVV + OM2_TVV + OM3_TVKA + EPS1 + EPS2 diff --git a/tests/testthat/_snaps/hyperion-summary-print.md b/tests/testthat/_snaps/hyperion-summary-print.md index c2cccd5e..1c2a1c56 100644 --- a/tests/testthat/_snaps/hyperion-summary-print.md +++ b/tests/testthat/_snaps/hyperion-summary-print.md @@ -268,15 +268,15 @@ Output - iteration method GRD.TVCL. GRD.TVV. GRD.TVKA. GRD.OM1..TVCL.. GRD.OM2..TVV.. GRD.EPS1. GRD.7. GRD.8. GRD.9. - ───────── ────── ───────── ──────── ────────── ─────────────── ────────────── ────────── ────────── ───────── ────────── - 0 FOCE 73.89 -28.82 -39.24 -19.1 -0.2895 -15.42 -3.366 -25.08 -1.02 - 5 FOCE 0.726 -0.4298 1.646 -1.356 -0.1662 -1.269 -1.461 -5.953 -0.471 - 10 FOCE -0.292 -0.1409 -0.6106 0.221 -0.1557 0.5171 0.04447 1.326 0.00219 - 15 FOCE -0.476 0.05529 0.08351 0.05463 -0.002578 0.3325 -0.03349 -0.8686 -0.1977 - 20 FOCE 0.07577 0.1036 0.01967 -0.01282 0.0001055 -0.02382 -0.03252 0.08144 0.05834 - 25 FOCE -0.09361 -0.2471 -0.06087 -0.002033 0.0001151 -0.007342 -0.005697 -0.006275 -0.002337 - 30 FOCE -0.002697 0.00215 -0.0007652 -0.0003618 0.000001674 -0.0002712 0.00001258 -0.00347 -0.0002455 + iteration method TVCL TVV TVKA OM1_TVCL OM1_2_TVCL_TVV OM2_TVV OM3_TVKA EPS1 EPS2 + ───────── ────── ───────── ─────── ────────── ────────── ────────────── ────────── ────────── ───────── ────────── + 0 FOCE 73.89 -28.82 -39.24 -19.1 -0.2895 -15.42 -3.366 -25.08 -1.02 + 5 FOCE 0.726 -0.4298 1.646 -1.356 -0.1662 -1.269 -1.461 -5.953 -0.471 + 10 FOCE -0.292 -0.1409 -0.6106 0.221 -0.1557 0.5171 0.04447 1.326 0.00219 + 15 FOCE -0.476 0.05529 0.08351 0.05463 -0.002578 0.3325 -0.03349 -0.8686 -0.1977 + 20 FOCE 0.07577 0.1036 0.01967 -0.01282 0.0001055 -0.02382 -0.03252 0.08144 0.05834 + 25 FOCE -0.09361 -0.2471 -0.06087 -0.002033 0.0001151 -0.007342 -0.005697 -0.006275 -0.002337 + 30 FOCE -0.002697 0.00215 -0.0007652 -0.0003618 0.000001674 -0.0002712 0.00001258 -0.00347 -0.0002455 # hyperion.nonmem-summary print works for run005 (not_run) @@ -324,13 +324,13 @@ Output - iteration method GRD.TVCL. GRD.TVV. GRD.TVKA. GRD.OM1..TVCL.. GRD.OM2..TVV.. GRD.EPS1. GRD.7. GRD.8. GRD.9. - ───────── ────── ───────── ──────── ────────── ─────────────── ────────────── ────────── ────────── ───────── ────────── - 0 FOCE 73.89 -28.82 -39.24 -19.1 -0.2895 -15.42 -3.366 -25.08 -1.02 - 5 FOCE 0.726 -0.4298 1.646 -1.356 -0.1662 -1.269 -1.461 -5.953 -0.471 - 10 FOCE -0.292 -0.1409 -0.6106 0.221 -0.1557 0.5171 0.04447 1.326 0.00219 - 15 FOCE -0.476 0.05529 0.08351 0.05463 -0.002578 0.3325 -0.03349 -0.8686 -0.1977 - 20 FOCE 0.07577 0.1036 0.01967 -0.01282 0.0001055 -0.02382 -0.03252 0.08144 0.05834 - 25 FOCE -0.09361 -0.2471 -0.06087 -0.002033 0.0001151 -0.007342 -0.005697 -0.006275 -0.002337 - 30 FOCE -0.002697 0.00215 -0.0007652 -0.0003618 0.000001674 -0.0002712 0.00001258 -0.00347 -0.0002455 + iteration method TVCL TVV TVKA OM1_TVCL OM1_2_TVCL_TVV OM2_TVV OM3_TVKA EPS1 EPS2 + ───────── ────── ───────── ─────── ────────── ────────── ────────────── ────────── ────────── ───────── ────────── + 0 FOCE 73.89 -28.82 -39.24 -19.1 -0.2895 -15.42 -3.366 -25.08 -1.02 + 5 FOCE 0.726 -0.4298 1.646 -1.356 -0.1662 -1.269 -1.461 -5.953 -0.471 + 10 FOCE -0.292 -0.1409 -0.6106 0.221 -0.1557 0.5171 0.04447 1.326 0.00219 + 15 FOCE -0.476 0.05529 0.08351 0.05463 -0.002578 0.3325 -0.03349 -0.8686 -0.1977 + 20 FOCE 0.07577 0.1036 0.01967 -0.01282 0.0001055 -0.02382 -0.03252 0.08144 0.05834 + 25 FOCE -0.09361 -0.2471 -0.06087 -0.002033 0.0001151 -0.007342 -0.005697 -0.006275 -0.002337 + 30 FOCE -0.002697 0.00215 -0.0007652 -0.0003618 0.000001674 -0.0002712 0.00001258 -0.00347 -0.0002455 From 937c43aaf669526c2494cd4cdd0fda0ca123d4d7 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Thu, 23 Apr 2026 14:44:36 -0400 Subject: [PATCH 22/41] fix read_ext_file syntactic names --- src/rust/nonmem/src/output_files/ext.rs | 21 +++++++---- src/rust/nonmem/src/output_files/grd.rs | 16 +++------ src/rust/nonmem/src/utils.rs | 14 ++++++++ .../summary-knit-run004-running.html | 18 +++++----- .../summary-knit-run004.html | 18 +++++----- .../testthat/_snaps/hyperion-summary-print.md | 36 +++++++++---------- 6 files changed, 69 insertions(+), 54 deletions(-) diff --git a/src/rust/nonmem/src/output_files/ext.rs b/src/rust/nonmem/src/output_files/ext.rs index 55faf77a..c09afa3b 100644 --- a/src/rust/nonmem/src/output_files/ext.rs +++ b/src/rust/nonmem/src/output_files/ext.rs @@ -8,7 +8,7 @@ use std::path::{Path, PathBuf}; use nonmem::estimation; use nonmem::output_files::ext::{EstimationTable, ExtReader}; -use crate::utils::find_output_file; +use crate::utils::{find_output_file, to_syntactic_name}; use hyperion_core::{OptionExt, ResultExt, extendr_err}; /// Extract .ext files from path (single file or directory) @@ -82,8 +82,12 @@ fn estimation_tables_to_dataframe(tables: Vec) -> Result return Err(extendr_err!("No tables found in ext file")); } - // Get parameter names from the first table - let param_names = tables[0].parameters.clone(); + // Get parameter names from the first table, sanitized for R syntactic use. + let param_names: Vec = tables[0] + .parameters + .iter() + .map(|n| to_syntactic_name(n)) + .collect(); let flat_data: Vec<(i32, String, Vec)> = tables .into_iter() @@ -317,10 +321,15 @@ pub fn get_final_estimates( return Err(extendr_err!("No tables found in ext file")); } - // Get parameter names from first table (all should be the same) - let param_names = if let Some((_, first_tables)) = results.first() { + // Get parameter names from first table (all should be the same), + // sanitized for R syntactic use. + let param_names: Vec = if let Some((_, first_tables)) = results.first() { if let Some(first_table) = first_tables.first() { - first_table.parameters.clone() + first_table + .parameters + .iter() + .map(|n| to_syntactic_name(n)) + .collect() } else { return Err(extendr_err!("No tables found in first ext file")); } diff --git a/src/rust/nonmem/src/output_files/grd.rs b/src/rust/nonmem/src/output_files/grd.rs index 6528e211..37ab158a 100644 --- a/src/rust/nonmem/src/output_files/grd.rs +++ b/src/rust/nonmem/src/output_files/grd.rs @@ -4,7 +4,9 @@ use extendr_api::prelude::*; //pharos nonmem crate use nonmem::{estimation::EstimationMethod, output_files::grd::GrdReader}; -use crate::utils::{find_output_file, get_comment_type, path_from_robj, try_parse_model}; +use crate::utils::{ + find_output_file, get_comment_type, path_from_robj, to_syntactic_name, try_parse_model, +}; use hyperion_core::{ResultExt, extendr_err}; /// Turn a pharos-produced gradient column name like `"GRD(IIV (CL))"` into a @@ -14,17 +16,7 @@ fn sanitize_grd_name(raw: &str) -> String { .strip_prefix("GRD(") .and_then(|s| s.strip_suffix(')')) .unwrap_or(raw); - - let mut out: String = inner - .chars() - .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' }) - .collect(); - - while out.contains("__") { - out = out.replace("__", "_"); - } - - out.trim_matches('_').to_string() + to_syntactic_name(inner) } fn create_grd_reader(only_method: Option<&str>, only_last: Option) -> Result { diff --git a/src/rust/nonmem/src/utils.rs b/src/rust/nonmem/src/utils.rs index fca11cea..fa2bc8f9 100644 --- a/src/rust/nonmem/src/utils.rs +++ b/src/rust/nonmem/src/utils.rs @@ -257,6 +257,20 @@ pub fn try_parse_model(path: &str) -> Option { /// Gets the comment type from pharos.toml configuration /// +/// Map an arbitrary string to an R-syntactic column name by replacing any +/// non-alphanumeric character with `_`, collapsing runs of `_`, and trimming +/// leading/trailing `_`. E.g. `"SIGMA(1,1)"` -> `"SIGMA_1_1"`. +pub(crate) fn to_syntactic_name(s: &str) -> String { + let mut out: String = s + .chars() + .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' }) + .collect(); + while out.contains("__") { + out = out.replace("__", "_"); + } + out.trim_matches('_').to_string() +} + /// @return Option from pharos config, None if not found or config doesn't exist pub fn get_comment_type() -> Option { find_config_dir() diff --git a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004-running.html b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004-running.html index 8027a1c9..6ccb1430 100644 --- a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004-running.html +++ b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004-running.html @@ -14,15 +14,15 @@ 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. + 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 diff --git a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004.html b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004.html index 8027a1c9..6ccb1430 100644 --- a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004.html +++ b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004.html @@ -14,15 +14,15 @@ 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. + 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 diff --git a/tests/testthat/_snaps/hyperion-summary-print.md b/tests/testthat/_snaps/hyperion-summary-print.md index 1c2a1c56..89cf6d02 100644 --- a/tests/testthat/_snaps/hyperion-summary-print.md +++ b/tests/testthat/_snaps/hyperion-summary-print.md @@ -253,15 +253,15 @@ Output - iteration method 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. - ───────── ────── ────── ────── ────── ────────── ────────── ────────── ────────── ────────── ────────── ────────── ────────── ────────── - 0 FOCE 1.612 36.18 1.004 0.03574 0 0.006 0.1 0.0001 0.1 0 0 0.1 - 5 FOCE 1.251 40.66 1.246 0.03667 0 0.006033 0.1262 0.000117 0.1325 0 0 0.1067 - 10 FOCE 1.247 40.73 1.24 0.03753 0 0.006265 0.1314 0.0004518 0.1371 0 0 0.114 - 15 FOCE 1.326 40.06 1.212 0.03914 0 0.000918 0.1261 0.07338 0.1228 0 0 0.1207 - 20 FOCE 1.325 40.09 1.21 0.03744 0 0.005545 0.1221 0.07462 0.1239 0 0 0.1223 - 25 FOCE 1.325 40.13 1.211 0.03754 0 0.005267 0.1223 0.07459 0.1239 0 0 0.1224 - 30 FOCE 1.325 40.16 1.212 0.03754 0 0.005272 0.1223 0.07454 0.1239 0 0 0.1224 + iteration method 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 + ───────── ────── ────── ────── ────── ───────── ───────── ───────── ───────── ───────── ───────── ───────── ───────── ───────── + 0 FOCE 1.612 36.18 1.004 0.03574 0 0.006 0.1 0.0001 0.1 0 0 0.1 + 5 FOCE 1.251 40.66 1.246 0.03667 0 0.006033 0.1262 0.000117 0.1325 0 0 0.1067 + 10 FOCE 1.247 40.73 1.24 0.03753 0 0.006265 0.1314 0.0004518 0.1371 0 0 0.114 + 15 FOCE 1.326 40.06 1.212 0.03914 0 0.000918 0.1261 0.07338 0.1228 0 0 0.1207 + 20 FOCE 1.325 40.09 1.21 0.03744 0 0.005545 0.1221 0.07462 0.1239 0 0 0.1223 + 25 FOCE 1.325 40.13 1.211 0.03754 0 0.005267 0.1223 0.07459 0.1239 0 0 0.1224 + 30 FOCE 1.325 40.16 1.212 0.03754 0 0.005272 0.1223 0.07454 0.1239 0 0 0.1224 Message -- Recent Gradients -- @@ -309,15 +309,15 @@ Output - iteration method 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. - ───────── ────── ────── ────── ────── ────────── ────────── ────────── ────────── ────────── ────────── ────────── ────────── ────────── - 0 FOCE 1.612 36.18 1.004 0.03574 0 0.006 0.1 0.0001 0.1 0 0 0.1 - 5 FOCE 1.251 40.66 1.246 0.03667 0 0.006033 0.1262 0.000117 0.1325 0 0 0.1067 - 10 FOCE 1.247 40.73 1.24 0.03753 0 0.006265 0.1314 0.0004518 0.1371 0 0 0.114 - 15 FOCE 1.326 40.06 1.212 0.03914 0 0.000918 0.1261 0.07338 0.1228 0 0 0.1207 - 20 FOCE 1.325 40.09 1.21 0.03744 0 0.005545 0.1221 0.07462 0.1239 0 0 0.1223 - 25 FOCE 1.325 40.13 1.211 0.03754 0 0.005267 0.1223 0.07459 0.1239 0 0 0.1224 - 30 FOCE 1.325 40.16 1.212 0.03754 0 0.005272 0.1223 0.07454 0.1239 0 0 0.1224 + iteration method 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 + ───────── ────── ────── ────── ────── ───────── ───────── ───────── ───────── ───────── ───────── ───────── ───────── ───────── + 0 FOCE 1.612 36.18 1.004 0.03574 0 0.006 0.1 0.0001 0.1 0 0 0.1 + 5 FOCE 1.251 40.66 1.246 0.03667 0 0.006033 0.1262 0.000117 0.1325 0 0 0.1067 + 10 FOCE 1.247 40.73 1.24 0.03753 0 0.006265 0.1314 0.0004518 0.1371 0 0 0.114 + 15 FOCE 1.326 40.06 1.212 0.03914 0 0.000918 0.1261 0.07338 0.1228 0 0 0.1207 + 20 FOCE 1.325 40.09 1.21 0.03744 0 0.005545 0.1221 0.07462 0.1239 0 0 0.1223 + 25 FOCE 1.325 40.13 1.211 0.03754 0 0.005267 0.1223 0.07459 0.1239 0 0 0.1224 + 30 FOCE 1.325 40.16 1.212 0.03754 0 0.005272 0.1223 0.07454 0.1239 0 0 0.1224 Message -- Recent Gradients -- From 701de1221c97a7cf481d4aab26ead70d86543361 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Mon, 27 Apr 2026 10:40:23 -0400 Subject: [PATCH 23/41] update pharos and fix metadata clear --- .Rbuildignore | 1 + .gitignore | 1 + NAMESPACE | 1 + R/extendr-wrappers.R | 27 ++++++- man/clear_metadata_file.Rd | 37 ++++++++++ man/copy_model.Rd | 3 + man/set_metadata_file.Rd | 10 ++- src/rust/Cargo.lock | 28 ++++---- src/rust/Cargo.toml | 8 +-- src/rust/nonmem/src/model/copy.rs | 3 + src/rust/nonmem/src/model/metadata.rs | 72 +++++++++++++++++-- .../summary-knit-run004-running.html | 4 +- .../summary-knit-run004.html | 4 +- .../testthat/_snaps/hyperion-summary-print.md | 4 +- 14 files changed, 172 insertions(+), 31 deletions(-) create mode 100644 man/clear_metadata_file.Rd diff --git a/.Rbuildignore b/.Rbuildignore index 83c09f98..5cf569b2 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -24,3 +24,4 @@ ^doc$ ^Meta$ ^_starlightr\.toml$ +^\.positai$ diff --git a/.gitignore b/.gitignore index 654856b2..b6d42449 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ lefthook.yaml ./pharos.toml /doc/ /Meta/ +.positai diff --git a/NAMESPACE b/NAMESPACE index 740c38eb..44891683 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -32,6 +32,7 @@ export(are_models_in_lineage) export(audit_parameter_info) export(check_dataset) export(check_model) +export(clear_metadata_file) export(compute_ci) export(compute_cv) export(compute_rse) diff --git a/R/extendr-wrappers.R b/R/extendr-wrappers.R index 3800a88c..7d311cd5 100644 --- a/R/extendr-wrappers.R +++ b/R/extendr-wrappers.R @@ -63,6 +63,7 @@ read_model_from_lst <- function(path) .Call(wrap__read_model_from_lst, path) #' Examples: "THETA1" or c("THETA1") #' @param seed integer for random number generator seed to ensure reproducible jittering #' @param description Description of model in metadata file +#' @param based_on Character vector of model names/paths that this model is based on #' @param no_metadata boolean, if true, does not create metadatafile, default FALSE #' #' @return path to new model file (invisible) todo @@ -71,7 +72,7 @@ read_model_from_lst <- function(path) .Call(wrap__read_model_from_lst, path) #' @examples \dontrun{ #' copy_model(from = "model/nonmem/run001.mod", to = "model/nonmem/run002.mod") #' } -copy_model <- function(from, to, overwrite = FALSE, ext_file = NULL, update = 'none', jitter = NULL, jitter_excluded = NULL, seed = NULL, description = NULL, no_metadata = FALSE) .Call(wrap__copy_model_wrap, from, to, overwrite, ext_file, update, jitter, jitter_excluded, seed, description, no_metadata) +copy_model <- function(from, to, overwrite = FALSE, ext_file = NULL, update = 'none', jitter = NULL, jitter_excluded = NULL, seed = NULL, description = NULL, based_on = NULL, no_metadata = FALSE) .Call(wrap__copy_model_wrap, from, to, overwrite, ext_file, update, jitter, jitter_excluded, seed, description, based_on, no_metadata) #' Gets model run summary (internal implementation) #' @@ -180,6 +181,7 @@ get_model_parameter_names <- function(model) .Call(wrap__get_model_parameter_nam #' @param description Optional description of the model and its purpose #' @param tags Character vector of tags to categorize or label the model #' @param based_on Character vector of model names/paths that this model is based on +#' @param copied_from Optional model name/path this model was mechanically copied from #' #' @return Returns invisibly after creating the metadata file #' @export @@ -201,7 +203,7 @@ get_model_parameter_names <- function(model) .Call(wrap__get_model_parameter_nam #' based_on = c("run001.mod") #' ) #' } -set_metadata_file <- function(model_path, description = NULL, tags = NULL, based_on = NULL) .Call(wrap__set_metadata_file, model_path, description, tags, based_on) +set_metadata_file <- function(model_path, description = NULL, tags = NULL, based_on = NULL, copied_from = NULL) .Call(wrap__set_metadata_file, model_path, description, tags, based_on, copied_from) #' Updates a metadatafile #' @@ -245,6 +247,27 @@ update_metadata_file <- function(model_path, description = NULL, tags = NULL, ba #' } get_model_metadata <- function(model) .Call(wrap__load_model_metadata, model) +#' Clear fields in a model's metadata file +#' +#' Selectively clears the `based_on`, `copied_from`, and/or `tags` fields in +#' the metadata file associated with a model. Fields not selected are left +#' unchanged. +#' +#' @param model_path Path to the NONMEM model file, or a hyperion_nonmem_model object +#' @param based_on If TRUE, clear the based_on field. Default FALSE. +#' @param copied_from If TRUE, clear the copied_from field. Default FALSE. +#' @param tags If TRUE, clear the tags field. Default FALSE. +#' +#' @return Returns invisibly after updating the metadata file +#' @export +#' +#' @examples \dontrun{ +#' clear_metadata_file("model/nonmem/run001.mod", tags = TRUE) +#' model <- read_model("model/nonmem/run001.mod") +#' clear_metadata_file(model, based_on = TRUE, copied_from = TRUE) +#' } +clear_metadata_file <- function(model_path, based_on = FALSE, copied_from = FALSE, tags = FALSE) .Call(wrap__clear_metadata_file_wrap, model_path, based_on, copied_from, tags) + #' Determine run status for a model path, run directory, or model object. #' #' @param input A hyperion_nonmem_model object, run directory, or model path. diff --git a/man/clear_metadata_file.Rd b/man/clear_metadata_file.Rd new file mode 100644 index 00000000..30d476e9 --- /dev/null +++ b/man/clear_metadata_file.Rd @@ -0,0 +1,37 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/extendr-wrappers.R +\name{clear_metadata_file} +\alias{clear_metadata_file} +\title{Clear fields in a model's metadata file} +\usage{ +clear_metadata_file( + model_path, + based_on = FALSE, + copied_from = FALSE, + tags = FALSE +) +} +\arguments{ +\item{model_path}{Path to the NONMEM model file, or a hyperion_nonmem_model object} + +\item{based_on}{If TRUE, clear the based_on field. Default FALSE.} + +\item{copied_from}{If TRUE, clear the copied_from field. Default FALSE.} + +\item{tags}{If TRUE, clear the tags field. Default FALSE.} +} +\value{ +Returns invisibly after updating the metadata file +} +\description{ +Selectively clears the \code{based_on}, \code{copied_from}, and/or \code{tags} fields in +the metadata file associated with a model. Fields not selected are left +unchanged. +} +\examples{ +\dontrun{ +clear_metadata_file("model/nonmem/run001.mod", tags = TRUE) +model <- read_model("model/nonmem/run001.mod") +clear_metadata_file(model, based_on = TRUE, copied_from = TRUE) +} +} diff --git a/man/copy_model.Rd b/man/copy_model.Rd index 3d6bcf5e..2642f6d0 100644 --- a/man/copy_model.Rd +++ b/man/copy_model.Rd @@ -14,6 +14,7 @@ copy_model( jitter_excluded = NULL, seed = NULL, description = NULL, + based_on = NULL, no_metadata = FALSE ) } @@ -40,6 +41,8 @@ Examples: "THETA1" or c("THETA1")} \item{description}{Description of model in metadata file} +\item{based_on}{Character vector of model names/paths that this model is based on} + \item{no_metadata}{boolean, if true, does not create metadatafile, default FALSE} } \value{ diff --git a/man/set_metadata_file.Rd b/man/set_metadata_file.Rd index 35414746..cb4cdc3d 100644 --- a/man/set_metadata_file.Rd +++ b/man/set_metadata_file.Rd @@ -4,7 +4,13 @@ \alias{set_metadata_file} \title{Creates a metadata file for a NONMEM model} \usage{ -set_metadata_file(model_path, description = NULL, tags = NULL, based_on = NULL) +set_metadata_file( + model_path, + description = NULL, + tags = NULL, + based_on = NULL, + copied_from = NULL +) } \arguments{ \item{model_path}{Path to the NONMEM model file, or a hyperion_nonmem_model object (required)} @@ -14,6 +20,8 @@ set_metadata_file(model_path, description = NULL, tags = NULL, based_on = NULL) \item{tags}{Character vector of tags to categorize or label the model} \item{based_on}{Character vector of model names/paths that this model is based on} + +\item{copied_from}{Optional model name/path this model was mechanically copied from} } \value{ Returns invisibly after creating the metadata file diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 1e1ab51e..bd1e51bc 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -52,9 +52,9 @@ checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "blake3" -version = "1.8.4" +version = "1.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d2d5991425dfd0785aed03aedcf0b321d61975c9b5b3689c774a2610ae0b51e" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" dependencies = [ "arrayref", "arrayvec", @@ -91,9 +91,9 @@ checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "cc" -version = "1.2.60" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", "shlex", @@ -141,7 +141,7 @@ dependencies = [ [[package]] name = "config" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#ffadc71d3e20ae4c14d35f9b337960ebd703f179" +source = "git+https://github.com/a2-ai/pharos?branch=metadata-update#aba478226b4438cabb5daf22b67b9a60d4ffd230" dependencies = [ "anyhow", "fs-err", @@ -284,7 +284,7 @@ dependencies = [ [[package]] name = "extendr-api" version = "0.9.0" -source = "git+https://github.com/extendr/extendr?branch=main#d800a2de950ba8b7b67902abc2d6d22b803fab2e" +source = "git+https://github.com/extendr/extendr?branch=main#b0cb8a811977f2a93147744f24fe22c5e1c630d9" dependencies = [ "extendr-ffi", "extendr-macros", @@ -297,12 +297,12 @@ dependencies = [ [[package]] name = "extendr-ffi" version = "0.9.0" -source = "git+https://github.com/extendr/extendr?branch=main#d800a2de950ba8b7b67902abc2d6d22b803fab2e" +source = "git+https://github.com/extendr/extendr?branch=main#b0cb8a811977f2a93147744f24fe22c5e1c630d9" [[package]] name = "extendr-macros" version = "0.9.0" -source = "git+https://github.com/extendr/extendr?branch=main#d800a2de950ba8b7b67902abc2d6d22b803fab2e" +source = "git+https://github.com/extendr/extendr?branch=main#b0cb8a811977f2a93147744f24fe22c5e1c630d9" dependencies = [ "lazy_static", "proc-macro2", @@ -648,9 +648,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.185" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libm" @@ -711,7 +711,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "nonmem" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#ffadc71d3e20ae4c14d35f9b337960ebd703f179" +source = "git+https://github.com/a2-ai/pharos?branch=metadata-update#aba478226b4438cabb5daf22b67b9a60d4ffd230" dependencies = [ "anyhow", "blake3", @@ -737,7 +737,7 @@ dependencies = [ [[package]] name = "nonmem-parser" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#ffadc71d3e20ae4c14d35f9b337960ebd703f179" +source = "git+https://github.com/a2-ai/pharos?branch=metadata-update#aba478226b4438cabb5daf22b67b9a60d4ffd230" dependencies = [ "anyhow", "distrs", @@ -1089,7 +1089,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#ffadc71d3e20ae4c14d35f9b337960ebd703f179" +source = "git+https://github.com/a2-ai/pharos?branch=metadata-update#aba478226b4438cabb5daf22b67b9a60d4ffd230" dependencies = [ "anyhow", "config", @@ -1335,7 +1335,7 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utils" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=sim-record#ffadc71d3e20ae4c14d35f9b337960ebd703f179" +source = "git+https://github.com/a2-ai/pharos?branch=metadata-update#aba478226b4438cabb5daf22b67b9a60d4ffd230" dependencies = [ "anyhow", "fs-err", diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 19f76858..ca7ad3de 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -38,10 +38,10 @@ serde = { workspace = true } extendr-api = { git = "https://github.com/extendr/extendr", branch = "main", features = ["serde"] } # Pharos components -nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "sim-record" } -nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", branch = "sim-record" } -config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "sim-record" } -scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "sim-record" } +nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "metadata-update" } +nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", branch = "metadata-update" } +config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "metadata-update" } +scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "metadata-update" } # Core utilities anyhow = "1.0.100" diff --git a/src/rust/nonmem/src/model/copy.rs b/src/rust/nonmem/src/model/copy.rs index e491c154..b4d19615 100644 --- a/src/rust/nonmem/src/model/copy.rs +++ b/src/rust/nonmem/src/model/copy.rs @@ -91,6 +91,7 @@ fn parse_update_robj(update: Robj) -> Result> { /// Examples: "THETA1" or c("THETA1") /// @param seed integer for random number generator seed to ensure reproducible jittering /// @param description Description of model in metadata file +/// @param based_on Character vector of model names/paths that this model is based on /// @param no_metadata boolean, if true, does not create metadatafile, default FALSE /// /// @return path to new model file (invisible) todo @@ -113,6 +114,7 @@ pub fn copy_model_wrap( #[extendr(default = "NULL")] jitter_excluded: Option<&Robj>, #[extendr(default = "NULL")] seed: Option, #[extendr(default = "NULL")] description: String, + #[extendr(default = "NULL")] based_on: Option>, #[extendr(default = "FALSE")] no_metadata: bool, ) -> Result<()> { // Parse input parameters @@ -130,6 +132,7 @@ pub fn copy_model_wrap( seed, jitter_excluded: jitter_excluded_parsed, description, + based_on: based_on.unwrap_or_default(), no_metadata, }; diff --git a/src/rust/nonmem/src/model/metadata.rs b/src/rust/nonmem/src/model/metadata.rs index 9a5b645d..0c7ff4ad 100644 --- a/src/rust/nonmem/src/model/metadata.rs +++ b/src/rust/nonmem/src/model/metadata.rs @@ -4,10 +4,12 @@ use extendr_api::serializer::to_robj; use nonmem::ModelMetadata; //pharos nonmem crate -use nonmem::update_metadata_file; +use nonmem::{clear_metadata_file, update_metadata_file}; use crate::utils::path_from_robj; -use hyperion_core::{ResultExt, extendr_err}; +use hyperion_core::{OptionExt, ResultExt, extendr_err}; + +const METADATA_FILENAME_SUFFIX: &str = "_metadata.json"; /// Creates a metadata file for a NONMEM model /// @@ -19,6 +21,7 @@ use hyperion_core::{ResultExt, extendr_err}; /// @param description Optional description of the model and its purpose /// @param tags Character vector of tags to categorize or label the model /// @param based_on Character vector of model names/paths that this model is based on +/// @param copied_from Optional model name/path this model was mechanically copied from /// /// @return Returns invisibly after creating the metadata file /// @export @@ -46,6 +49,7 @@ pub fn set_metadata_file( #[extendr(default = "NULL")] description: Option, #[extendr(default = "NULL")] tags: Option>, #[extendr(default = "NULL")] based_on: Option>, + #[extendr(default = "NULL")] copied_from: Option, ) -> Result<()> { if let Some(d) = &description && d.trim().is_empty() @@ -60,7 +64,7 @@ pub fn set_metadata_file( let tags = tags.unwrap_or_default(); let based_on = based_on.unwrap_or_default(); - update_metadata_file(model_path, description, tags, based_on, true) + update_metadata_file(model_path, description, tags, based_on, copied_from, true) .map_to_extendr_err("Failed to create metadata file")?; Ok(()) @@ -94,7 +98,7 @@ pub fn append_to_metadata_file( let tags = tags.unwrap_or_default(); let based_on = based_on.unwrap_or_default(); - update_metadata_file(path, description, tags, based_on, false) + update_metadata_file(path, description, tags, based_on, None, false) .map_to_extendr_err("Failed to update metadata file")?; Ok(()) @@ -139,10 +143,70 @@ pub fn load_model_metadata(model: Robj) -> Result { Ok(result.to_owned()) } +/// Clear fields in a model's metadata file +/// +/// Selectively clears the `based_on`, `copied_from`, and/or `tags` fields in +/// the metadata file associated with a model. Fields not selected are left +/// unchanged. +/// +/// @param model_path Path to the NONMEM model file, or a hyperion_nonmem_model object +/// @param based_on If TRUE, clear the based_on field. Default FALSE. +/// @param copied_from If TRUE, clear the copied_from field. Default FALSE. +/// @param tags If TRUE, clear the tags field. Default FALSE. +/// +/// @return Returns invisibly after updating the metadata file +/// @export +/// +/// @examples \dontrun{ +/// clear_metadata_file("model/nonmem/run001.mod", tags = TRUE) +/// model <- read_model("model/nonmem/run001.mod") +/// clear_metadata_file(model, based_on = TRUE, copied_from = TRUE) +/// } +#[extendr(r_name = "clear_metadata_file")] +pub fn clear_metadata_file_wrap( + model_path: Robj, + #[extendr(default = "FALSE")] based_on: bool, + #[extendr(default = "FALSE")] copied_from: bool, + #[extendr(default = "FALSE")] tags: bool, +) -> Result<()> { + let model_path = path_from_robj(&model_path, true)?; + + let model_name = model_path + .file_stem() + .ok_or_extendr_err("Could not determine model file stem")? + .to_string_lossy() + .to_string(); + let model_dir = model_path + .parent() + .ok_or_extendr_err("Could not determine model directory")? + .to_path_buf(); + let metadata_path = model_dir.join(format!("{model_name}{METADATA_FILENAME_SUFFIX}")); + + if !metadata_path.exists() { + return Err(extendr_err!( + "Metadata file does not exist: {}", + metadata_path.display() + )); + } + + clear_metadata_file( + model_name, + &model_dir, + &metadata_path, + based_on, + copied_from, + tags, + ) + .map_to_extendr_err("Failed to clear metadata file")?; + + Ok(()) +} + extendr_module! { mod metadata; fn set_metadata_file; fn append_to_metadata_file; fn load_model_metadata; + fn clear_metadata_file_wrap; } diff --git a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004-running.html b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004-running.html index 6ccb1430..24ac0e6d 100644 --- a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004-running.html +++ b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004-running.html @@ -156,8 +156,8 @@ OM1_2_TVCL_TVV OM2_TVV OM3_TVKA - EPS1 - EPS2 + SIGMA_1_1 + SIGMA_2_2 diff --git a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004.html b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004.html index 6ccb1430..24ac0e6d 100644 --- a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004.html +++ b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run004.html @@ -156,8 +156,8 @@ OM1_2_TVCL_TVV OM2_TVV OM3_TVKA - EPS1 - EPS2 + SIGMA_1_1 + SIGMA_2_2 diff --git a/tests/testthat/_snaps/hyperion-summary-print.md b/tests/testthat/_snaps/hyperion-summary-print.md index 89cf6d02..04ec4c05 100644 --- a/tests/testthat/_snaps/hyperion-summary-print.md +++ b/tests/testthat/_snaps/hyperion-summary-print.md @@ -268,7 +268,7 @@ Output - iteration method TVCL TVV TVKA OM1_TVCL OM1_2_TVCL_TVV OM2_TVV OM3_TVKA EPS1 EPS2 + iteration method TVCL TVV TVKA OM1_TVCL OM1_2_TVCL_TVV OM2_TVV OM3_TVKA SIGMA_1_1 SIGMA_2_2 ───────── ────── ───────── ─────── ────────── ────────── ────────────── ────────── ────────── ───────── ────────── 0 FOCE 73.89 -28.82 -39.24 -19.1 -0.2895 -15.42 -3.366 -25.08 -1.02 5 FOCE 0.726 -0.4298 1.646 -1.356 -0.1662 -1.269 -1.461 -5.953 -0.471 @@ -324,7 +324,7 @@ Output - iteration method TVCL TVV TVKA OM1_TVCL OM1_2_TVCL_TVV OM2_TVV OM3_TVKA EPS1 EPS2 + iteration method TVCL TVV TVKA OM1_TVCL OM1_2_TVCL_TVV OM2_TVV OM3_TVKA SIGMA_1_1 SIGMA_2_2 ───────── ────── ───────── ─────── ────────── ────────── ────────────── ────────── ────────── ───────── ────────── 0 FOCE 73.89 -28.82 -39.24 -19.1 -0.2895 -15.42 -3.366 -25.08 -1.02 5 FOCE 0.726 -0.4298 1.646 -1.356 -0.1662 -1.269 -1.461 -5.953 -0.471 From 98292599f5a576e8a6be67581c920c3d0d049647 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Thu, 30 Apr 2026 16:09:46 -0400 Subject: [PATCH 24/41] cleanup --- DESCRIPTION | 2 +- R/comments-classes.R | 118 +-- R/comments-lookup.R | 15 +- R/comments-parsing.R | 530 ------------ R/comments-query.R | 52 +- R/comments-utils.R | 45 + R/extendr-wrappers.R | 19 +- R/parameter-info.R | 157 ++++ inst/extdata/data/derived/PK_Oral_Ex1.csv | 801 ------------------ inst/extdata/mod/1001.mod | 4 +- inst/extdata/models/onecmt/run003.mod | 2 +- inst/extdata/models/onecmt/run003/run003.lst | 2 +- inst/extdata/models/onecmt/run003/run003.mod | 2 +- inst/extdata/models/onecmt/run003b1.mod | 2 +- .../models/onecmt/run003b1/run003b1.lst | 2 +- .../models/onecmt/run003b1/run003b1.mod | 2 +- inst/extdata/models/onecmt/run003b2.mod | 2 +- inst/extdata/models/onecmt/run004.mod | 2 +- inst/pharos.toml | 2 +- man/copy_model.Rd | 2 +- man/get_model_comment_info.Rd | 19 + man/get_model_parameter_info.Rd | 2 +- man/map_parameterization.Rd | 19 + src/rust/Cargo.lock | 80 +- src/rust/Cargo.toml | 10 +- src/rust/nonmem/Cargo.toml | 1 + src/rust/nonmem/src/model/comment_info.rs | 215 +++++ src/rust/nonmem/src/model/copy.rs | 2 +- src/rust/nonmem/src/model/mod.rs | 2 + src/rust/nonmem/src/model/parameters.rs | 2 +- .../nonmem/src/output_files/transforms.rs | 2 +- .../hyperion-model-knit/model-knit-1001.html | 4 +- tests/testthat/_snaps/hyperion-model-print.md | 10 +- .../parameter-info-knit-run001.html | 6 +- .../parameter-info-knit-run002.html | 6 +- .../parameter-info-knit-run003.html | 8 +- .../parameter-info-knit-run003b1.html | 8 +- .../_snaps/hyperion-parameter-info-print.md | 54 +- .../summary-knit-run003.html | 2 +- .../summary-knit-run003b1.html | 2 +- .../testthat/_snaps/hyperion-summary-print.md | 24 +- tests/testthat/test-comments-query-edge.R | 10 + tests/testthat/test-comments-validation.R | 65 -- tests/testthat/test-split-theta-reference.R | 7 - tests/testthat/test-split_theta_reference.R | 15 - 45 files changed, 652 insertions(+), 1686 deletions(-) delete mode 100644 R/comments-parsing.R create mode 100644 R/parameter-info.R delete mode 100644 inst/extdata/data/derived/PK_Oral_Ex1.csv create mode 100644 man/get_model_comment_info.Rd create mode 100644 man/map_parameterization.Rd create mode 100644 src/rust/nonmem/src/model/comment_info.rs delete mode 100644 tests/testthat/test-comments-validation.R delete mode 100644 tests/testthat/test-split-theta-reference.R delete mode 100644 tests/testthat/test-split_theta_reference.R diff --git a/DESCRIPTION b/DESCRIPTION index de61840f..d1d29727 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -18,7 +18,7 @@ License: MIT + file LICENSE Encoding: UTF-8 Roxygen: list(markdown = TRUE) RoxygenNote: 7.3.3 -Config/rextendr/version: 0.4.2 +Config/rextendr/version: 0.5.0 Config/build/never-clean: true Config/build/extra-sources: src/rust/Cargo.lock SystemRequirements: Cargo (Rust's package manager), rustc >= 1.65.0, xz diff --git a/R/comments-classes.R b/R/comments-classes.R index d92ee85c..2fc8cfb7 100644 --- a/R/comments-classes.R +++ b/R/comments-classes.R @@ -1,39 +1,3 @@ -#' Map raw parameterization string to Transform name -#' -#' @param raw_param Raw parameterization string from comment (e.g., "EXP", ":EXP") -#' @param kind Parameter kind: "THETA", "OMEGA", or "SIGMA" -#' @return Mapped parameterization name or NULL if not recognized -#' @noRd -map_parameterization <- function(raw_param, kind) { - if (is.null(raw_param) || !nzchar(raw_param)) { - return(NULL) - } - - # Remove leading colon if present and convert to uppercase - cleaned <- toupper(gsub("^:", "", trimws(raw_param))) - - # Common mappings for all parameter types - switch( - EXPR = cleaned, - "EXP" = "LogNormal", - "LOG" = "LogNormal", - "LOGNORMAL" = "LogNormal", - "LOGIT" = "Logit", - "ADD" = "AddErr", - "ADDERR" = "AddErr", - "ADDITIVE" = "AddErr", - "LOGADD" = "LogAddErr", - "LOGADDERR" = "LogAddErr", - "LOGERR" = "LogAddErr", - "PROP" = "Proportional", - "PROPORTIONAL" = "Proportional", - "IDENTITY" = "Identity", - "NORMAL" = "Identity", - "NONE" = "Identity", - NULL - ) -} - #' @noRd ParameterComment <- S7::new_class( "ParameterComment", @@ -92,53 +56,6 @@ make_tracked_property <- function(field_name, valid_values = NULL) { ) } -#' Normalize omega associated_theta values against theta names -#' -#' If no exact match is found, tries matching by stripping trailing "/...". -#' Only applies when the base name maps unambiguously to a single theta name. -#' -#' @param assoc Character vector of associated theta names -#' @param theta_names Character vector of theta names -#' @return Character vector of normalized associated theta names -#' @noRd -normalize_associated_theta <- function(assoc, theta_names) { - if (length(theta_names) == 0 || length(assoc) == 0) { - return(assoc) - } - - exact_lookup <- stats::setNames(theta_names, tolower(theta_names)) - - base_names <- sub("/.*$", "", theta_names) - base_lc <- tolower(base_names) - base_map <- list() - for (i in seq_along(theta_names)) { - base_map[[base_lc[i]]] <- c(base_map[[base_lc[i]]], theta_names[i]) - } - base_lookup <- vapply( - base_map, - function(vals) { - if (length(unique(vals)) == 1) unique(vals) else NA_character_ - }, - character(1) - ) - base_lookup <- base_lookup[!is.na(base_lookup)] - - vapply( - assoc, - function(theta) { - key <- tolower(theta) - if (key %in% names(exact_lookup)) { - exact_lookup[[key]] - } else if (key %in% names(base_lookup)) { - base_lookup[[key]] - } else { - theta - } - }, - character(1) - ) -} - #' Theta parameter comment class #' #' Represents parsed comments for THETA parameters. @@ -342,11 +259,7 @@ ModelComments <- S7::new_class( comment <- omega_comments[[omega_name]] if (!is.null(comment@associated_theta)) { assoc <- comment@associated_theta - sources <- attr(comment, "sources") %||% list() - assoc_source <- sources[["associated_theta"]] %||% "default" - assoc_norm <- normalize_associated_theta(assoc, theta_names) - # Validate against normalized associated_theta without mutating state. - missing <- setdiff(assoc_norm, theta_names) + missing <- setdiff(assoc, theta_names) if (length(missing) > 0) { errors <- c( errors, @@ -415,35 +328,6 @@ ModelComments <- S7::new_class( NULL }, constructor = function(theta = list(), omega = list(), sigma = list()) { - if (length(theta) > 0 && length(omega) > 0) { - theta_names <- vapply( - theta, - function(c) if (is.null(c@name)) NA_character_ else c@name, - character(1) - ) - theta_names <- theta_names[!is.na(theta_names)] - if (length(theta_names) > 0) { - omega <- lapply(omega, function(comment) { - if (!is.null(comment@associated_theta)) { - assoc <- comment@associated_theta - sources <- attr(comment, "sources") %||% list() - assoc_source <- sources[["associated_theta"]] %||% "default" - assoc_norm <- normalize_associated_theta(assoc, theta_names) - assoc_norm <- unname(assoc_norm) - if (!identical(unname(assoc), assoc_norm)) { - comment@associated_theta <- assoc_norm - sources[["associated_theta"]] <- paste0( - "normalized from ", - assoc_source - ) - attr(comment, "sources") <- sources - } - } - comment - }) - } - } - S7::new_object( S7::S7_object(), theta = theta, diff --git a/R/comments-lookup.R b/R/comments-lookup.R index 596b696e..041fb930 100644 --- a/R/comments-lookup.R +++ b/R/comments-lookup.R @@ -97,15 +97,8 @@ apply_lookup_defaults <- function(comment, lookup_path) { if (is.null(comment@parameterization) && !is.null(entry$parameterization)) { if (entry$parameterization != "none") { - kind <- if (is_theta) { - "THETA" - } else if (is_omega) { - "OMEGA" - } else { - "SIGMA" - } - mapped <- map_parameterization(entry$parameterization, kind) - if (!is.null(mapped)) { + mapped <- map_parameterization(entry$parameterization) + if (!is.na(mapped)) { comment@parameterization <- mapped attr(comment, "sources")$parameterization <- lookup_path } @@ -192,8 +185,8 @@ add_parameter_to_lookup <- function( # Validate parameterization if provided if (!is.null(parameterization)) { - mapped <- map_parameterization(parameterization, "THETA") - if (is.null(mapped)) { + mapped <- map_parameterization(parameterization) + if (is.na(mapped)) { rlang::abort(paste0( "Invalid parameterization: ", parameterization, diff --git a/R/comments-parsing.R b/R/comments-parsing.R deleted file mode 100644 index 66ae2fb3..00000000 --- a/R/comments-parsing.R +++ /dev/null @@ -1,530 +0,0 @@ -#' Make a path relative to project root (pharos.toml directory) -#' @noRd -relative_path <- function(path) { - if (is.null(path) || path == "default" || path == "user supplied") { - return(path) - } - tryCatch( - { - config_path <- find_pharos_config_file() - if (grepl("No pharos.toml", config_path)) { - return(path) - } - root <- fs::path_dir(config_path) - as.character(fs::path_rel(path, start = root)) - }, - error = function(e) path - ) -} - -#' Set source paths for comment fields -#' -#' Always initializes the sources attribute to mark object as "initialized". -#' Fields with non-NULL values get source_path; NULL fields get "default". -#' @noRd -set_sources <- function(comment, fields, source_path) { - source_path <- relative_path(source_path) - sources <- list() - for (f in fields) { - val <- S7::prop(comment, f) - if (!is.null(val)) { - sources[[f]] <- source_path - } else { - sources[[f]] <- "default" - } - } - attr(comment, "sources") <- sources - comment -} - -#' @noRd -normalize_comment_name <- function(name) { - if (!is.null(name) && (!nzchar(name) || is.na(name))) { - return(NULL) - } - name -} - -#' @noRd -create_comment_with_sources <- function(constructor, fields, mod_path, ...) { - comment <- constructor(...) - set_sources(comment, fields, mod_path) -} - -#' Find and read model from .lst file in a directory -#' @noRd -read_model_from_lst_dir <- function(dir_path) { - lst_candidates <- list.files( - dir_path, - pattern = "\\.lst$", - ignore.case = TRUE, - full.names = TRUE - ) - if (length(lst_candidates) == 0) { - rlang::abort(paste0("lst file not found in run directory: ", dir_path)) - } - read_model_from_lst(lst_candidates[1]) -} - -#' Derive output directory from model path and read from .lst file -#' @noRd -read_model_from_lst_path <- function(mod_path) { - mod_path <- from_config_relative(mod_path) - # Derive output directory: run001.mod -> run001/ - base_name <- tools::file_path_sans_ext(basename(mod_path)) - parent_dir <- dirname(mod_path) - output_dir <- file.path(parent_dir, base_name) - - if (!dir.exists(output_dir)) { - rlang::abort(paste0( - "Output directory not found for model: ", - mod_path, - "\nExpected: ", - output_dir - )) - } - - read_model_from_lst_dir(output_dir) -} - -#' Extract all parameter comments from a model as ModelComments object -#' -#' Parses parameter comments and returns structured metadata for theta, omega, -#' and sigma parameters. -#' -#' For model objects sourced from `.mod`/`.ctl` files: -#' - if run status is `"run"`, metadata is read from the corresponding `.lst` -#' - otherwise (`"not_run"`/`"running"`), metadata is read from the model file -#' -#' @param mod A hyperion_nonmem_model object or path to a run output directory -#' containing an .lst file. -#' @param lookup_path Optional path to a TOML lookup file. If provided, fills -#' NULL fields (display, description, unit, parameterization) from the lookup. -#' -#' @return A `ModelComments` object containing theta, omega, and sigma comments. -#' -#' @section Comment Parsing: -#' Comments are parsed by pharos according to the `[nonmem.comments]` section -#' of `pharos.toml`. Set `type = "type1"` for strict structured comments, or -#' `type = "type2"` for a more flexible structured grammar. See pharos -#' documentation for accepted formats. -#' -#' @seealso [get_parameter_transform()], [get_theta_names()], [get_comment()] -#' @export -get_model_parameter_info <- function(mod, lookup_path = NULL) { - if (is.character(mod) && length(mod) == 1) { - mod_path <- normalizePath(mod, mustWork = FALSE) - if (!dir.exists(mod_path)) { - rlang::abort(paste0( - "mod must be a run output directory containing an .lst file: ", - mod_path - )) - } - mod <- read_model_from_lst_dir(mod_path) - } else if (inherits(mod, "hyperion_nonmem_model")) { - mod_path <- attr(mod, "model_source") %||% "unknown" - if (!identical(mod_path, "unknown")) { - mod_path <- from_config_relative(mod_path) - } - # If model was read from .mod/.ctl file: - # - use .lst for completed runs - # - keep model object for not_run/running - if (!grepl("\\.lst$", mod_path, ignore.case = TRUE)) { - run_status <- refresh_run_status(mod) - if (identical(run_status, "run")) { - if (identical(mod_path, "unknown")) { - rlang::abort( - "Cannot locate .lst for completed run: model_source attribute is missing." - ) - } - # Derive output directory from model path (e.g., run001.mod -> run001/) - mod <- read_model_from_lst_path(mod_path) - } else if (!run_status %in% c("not_run", "running")) { - rlang::abort(paste0( - "model run_status must be 'run', 'running', or 'not_run', got: ", - run_status - )) - } - } - } else { - rlang::abort( - "mod must be a hyperion_nonmem_model object or path to a run output directory containing an .lst file" - ) - } - - mod_path <- attr(mod, "model_source") %||% "unknown" - if (!identical(mod_path, "unknown")) { - mod_path <- from_config_relative(mod_path) - } - - param_names <- get_model_parameter_names(mod) - parsed_comments <- extract_comments(mod) - comments <- parse_comments(param_names, parsed_comments, mod_path) - - # Split into theta, omega, sigma - theta_comments <- comments[grepl("^THETA", names(comments))] - omega_comments <- comments[grepl("^OMEGA", names(comments))] - sigma_comments <- comments[grepl("^SIGMA", names(comments))] - - # Create ModelComments object (this does duplicate omega name renaming) - result <- ModelComments( - theta = theta_comments, - omega = omega_comments, - sigma = sigma_comments - ) - - # Apply lookup AFTER renaming so "IIV-CL/F" matches lookup keys - if (!is.null(lookup_path)) { - lookup_path <- normalizePath(lookup_path, mustWork = FALSE) - result <- apply_lookup(result, lookup_path) - } - - result -} - -#' @noRd -extract_comments <- function(mod) { - parsed <- list() - raw <- list() - - for (i in seq_along(mod$thetas)) { - old_name <- paste0("THETA", i) - parsed[[old_name]] <- mod$thetas[[i]]$parsed_comment - raw[[old_name]] <- mod$thetas[[i]]$comment - } - - result <- extract_block_comments(parsed, raw, mod$omega_blocks, "OMEGA") - parsed <- result$parsed - raw <- result$raw - - result <- extract_block_comments(parsed, raw, mod$sigma_blocks, "SIGMA") - - result$parsed -} - -#' Unwrap ParsedRaneffComment enum (Omega/Sigma wrapper) to get the inner -#' parsed comment that the type1/type2 parsing functions expect. -#' @noRd -unwrap_raneff_comment <- function(parsed_comment) { - if (is.null(parsed_comment)) { - return(NULL) - } - parsed_comment$Omega %||% parsed_comment$Sigma %||% parsed_comment -} - -#' @noRd -extract_block_comments <- function(parsed, raw, blocks, prefix) { - row <- 1 - - for (block in blocks) { - struct <- block$structure - - # Handle structure as string "Diagonal" or list with named element - is_diagonal <- identical(struct, "Diagonal") || - (is.list(struct) && "Diagonal" %in% names(struct)) - is_block <- is.list(struct) && "Block" %in% names(struct) - is_block_same <- is.list(struct) && "BlockSame" %in% names(struct) - - if (is_diagonal) { - for (param in block$parameters) { - old_name <- sprintf("%s(%d,%d)", prefix, row, row) - parsed[[old_name]] <- unwrap_raneff_comment(param$parsed_comment) - raw[[old_name]] <- param$comment - row <- row + 1 - } - } else if (is_block) { - block_size <- struct$Block$size - param_idx <- 1 - start_row <- row - - for (i in seq_len(block_size)) { - # Track elements on this row - row_names <- character(i) - - for (j in seq_len(i)) { - old_name <- sprintf( - "%s(%d,%d)", - prefix, - start_row + i - 1, - start_row + j - 1 - ) - row_names[j] <- old_name - parsed[[old_name]] <- unwrap_raneff_comment( - block$parameters[[param_idx]]$parsed_comment - ) - raw[[old_name]] <- block$parameters[[param_idx]]$comment - param_idx <- param_idx + 1 - } - - # Clear duplicate comments from off-diagonals - # (when elements share a source line, they all get the same comment from parser) - if (i > 1) { - diag_name <- row_names[i] # Last element is diagonal (j == i) - diag_comment <- raw[[diag_name]] - if (!is.null(diag_comment) && nzchar(diag_comment)) { - for (k in seq_len(i - 1)) { - off_diag_name <- row_names[k] - if (identical(raw[[off_diag_name]], diag_comment)) { - raw[[off_diag_name]] <- NULL - parsed[[off_diag_name]] <- NULL - } - } - } - } - } - row <- start_row + block_size - } else if (is_block_same) { - block_size <- struct$BlockSame$size - row <- row + block_size - } - } - - list(parsed = parsed, raw = raw) -} - -#' Parse structured (typed) comments from model -#' @noRd -parse_comments <- function(param_names, parsed_comments, mod_path) { - nonmem_names <- names(param_names) - - # First pass: parse thetas to collect known theta names - theta_names <- nonmem_names[grepl("^THETA", nonmem_names)] - theta_comments <- lapply(theta_names, function(nonmem_name) { - name <- param_names[[nonmem_name]] - parsed <- parsed_comments[[nonmem_name]] - parse_typed_theta_comment(nonmem_name, name, parsed, mod_path) - }) - names(theta_comments) <- theta_names - - known_thetas <- vapply( - theta_comments, - function(c) c@name %||% "", - character(1) - ) - known_thetas <- known_thetas[nzchar(known_thetas)] - - # Second pass: parse omega/sigma with known_thetas context - other_names <- nonmem_names[!grepl("^THETA", nonmem_names)] - other_comments <- lapply(other_names, function(nonmem_name) { - name <- param_names[[nonmem_name]] - parsed <- parsed_comments[[nonmem_name]] - - if (grepl("^OMEGA", nonmem_name)) { - parse_typed_omega_comment( - nonmem_name, - name, - parsed, - mod_path, - known_thetas - ) - } else if (grepl("^SIGMA", nonmem_name)) { - parse_typed_sigma_comment(nonmem_name, name, parsed, mod_path) - } else { - rlang::abort(paste0("Unknown parameter type: ", nonmem_name)) - } - }) - names(other_comments) <- other_names - - comments <- c(theta_comments, other_comments) - comments[nonmem_names] -} - -#' @noRd -parse_typed_theta_comment <- function(nonmem_name, name, parsed, mod_path) { - name <- normalize_comment_name(name) - - unit <- NULL - parameterization <- NULL - - if (!is.null(parsed)) { - if (!is.null(parsed$Type1)) { - type1 <- parsed$Type1 - if (!is.null(type1$WithUnit)) { - if (is.null(name)) { - name <- type1$WithUnit$parameter - } - unit <- type1$WithUnit$unit - parameterization <- map_parameterization( - type1$WithUnit$parametrization, - "THETA" - ) - } else if (!is.null(type1$Type)) { - if (is.null(name)) { - name <- type1$Type$typ - } - parameterization <- map_parameterization( - type1$Type$parameterization, - "THETA" - ) - } else if (!is.null(type1$Covariate)) { - if (is.null(name)) name <- type1$Covariate$parameter - } - } else if (!is.null(parsed$Type2)) { - type2 <- parsed$Type2 - if (is.null(name)) { - name <- type2$name - } - unit <- type2$unit - parameterization <- type2$parameterization - } - } - - create_comment_with_sources( - ThetaComment, - theta_fields(), - mod_path, - nonmem_name = nonmem_name, - name = name, - unit = unit, - parameterization = parameterization - ) -} - -#' Check if an omega parameter is diagonal (variance) vs off-diagonal (covariance) -#' @noRd -is_diagonal_omega <- function(nonmem_name) { - # Parse OMEGA(i,j) format - match <- regmatches( - nonmem_name, - regexec("OMEGA\\((\\d+),(\\d+)\\)", nonmem_name) - )[[1]] - if (length(match) == 3) { - return(match[2] == match[3]) - } - # If we can't parse, assume diagonal - TRUE -} - -#' @noRd -parse_typed_omega_comment <- function( - nonmem_name, - name, - parsed, - mod_path, - known_thetas = NULL -) { - name <- normalize_comment_name(name) - - parameterization <- NULL - associated_theta <- NULL - - # Pharos formats @name as "Name (theta_ref)"; keep it verbatim but also - # extract associated_theta from the parens for downstream queries. - if (!is.null(name) && grepl("\\(.*\\)", name)) { - theta_part <- gsub(".*\\((.+)\\).*", "\\1", name) - associated_theta <- split_theta_reference(theta_part, known_thetas) - } - - if (!is.null(parsed)) { - if (!is.null(parsed$Type1)) { - type1 <- parsed$Type1 - if (is.null(associated_theta)) { - associated_theta <- type1$theta_name - } - parameterization <- map_parameterization( - type1$parameterization, - "OMEGA" - ) - } else if (!is.null(parsed$Type2)) { - type2 <- parsed$Type2 - if (is.null(associated_theta)) { - associated_theta <- type2$raw_theta_refs - } - parameterization <- type2$parameterization - } - } - - create_comment_with_sources( - OmegaComment, - omega_fields(), - mod_path, - nonmem_name = nonmem_name, - name = name, - parameterization = parameterization, - associated_theta = associated_theta - ) -} - -#' @noRd -parse_typed_sigma_comment <- function(nonmem_name, name, parsed, mod_path) { - name <- normalize_comment_name(name) - - unit <- NULL - parameterization <- NULL - - if (!is.null(parsed)) { - if (!is.null(parsed$Type1)) { - type1 <- parsed$Type1 - if (is.null(name)) { - name <- type1$name - } - parameterization <- map_parameterization( - type1$parameterization, - "SIGMA" - ) - } else if (!is.null(parsed$Type2)) { - type2 <- parsed$Type2 - if (is.null(name)) { - name <- type2$name - } - unit <- type2$unit - parameterization <- type2$parameterization - } - } - - create_comment_with_sources( - SigmaComment, - sigma_fields(), - mod_path, - nonmem_name = nonmem_name, - name = name, - unit = unit, - parameterization = parameterization - ) -} - -#' Split theta reference into associated thetas -#' -#' Splits on separators unless the string matches a known theta name (case-insensitive). -#' -#' @param theta_ref Character string of the theta reference -#' @param known_thetas Character vector of known theta names for context -#' @return Character vector of associated theta names -#' @noRd -split_theta_reference <- function(theta_ref, known_thetas = NULL) { - if (is.null(theta_ref) || !nzchar(theta_ref)) { - return(NULL) - } - - theta_ref <- trimws(theta_ref) - - # Check if it matches a known theta (case-insensitive) - if (!is.null(known_thetas) && length(known_thetas) > 0) { - if (tolower(theta_ref) %in% tolower(known_thetas)) { - return(theta_ref) - } - - # Preserve off-diagonal pairs like "CL/F-V2/F" when both parts - # are known theta names. - for (sep in c("-", ",", ":")) { - if (grepl(sep, theta_ref, fixed = TRUE)) { - parts <- trimws(strsplit(theta_ref, sep, fixed = TRUE)[[1]]) - if ( - length(parts) == 2 && - all(nzchar(parts)) && - all(tolower(parts) %in% tolower(known_thetas)) - ) { - return(parts) - } - } - } - } - - # Otherwise split on separators - if (grepl("[-/:,]", theta_ref)) { - parts <- strsplit(theta_ref, "[-/:,]")[[1]] - return(trimws(parts)) - } - - theta_ref -} diff --git a/R/comments-query.R b/R/comments-query.R index 9304ac8f..d594aa3d 100644 --- a/R/comments-query.R +++ b/R/comments-query.R @@ -37,53 +37,33 @@ get_comment <- function(model_comments, nonmem_name) { NULL } +#' Check if an omega parameter is diagonal (variance) vs off-diagonal (covariance) #' @noRd -build_comment_lookup <- function(model_comments) { - all_comments <- c( - model_comments@theta, - model_comments@omega, - model_comments@sigma - ) - - by_user_name <- list() - comments_by_kind <- list( - THETA = model_comments@theta, - OMEGA = model_comments@omega, - SIGMA = model_comments@sigma - ) - for (kind in names(comments_by_kind)) { - for (comment in comments_by_kind[[kind]]) { - if (!is.null(comment@name)) { - if (is.null(by_user_name[[comment@name]])) { - by_user_name[[comment@name]] <- comment - } else { - rlang::warn( - paste0( - "Duplicate parameter name '", - comment@name, - "' across parameter kinds; using first occurrence (", - kind, - ")." - ) - ) - } - } - } +is_diagonal_omega <- function(nonmem_name) { + # Parse OMEGA(i,j) format + match <- regmatches( + nonmem_name, + regexec("OMEGA\\((\\d+),(\\d+)\\)", nonmem_name) + )[[1]] + if (length(match) == 3) { + return(match[2] == match[3]) } - - list(by_nonmem_name = all_comments, by_user_name = by_user_name) + # If we can't parse, assume diagonal + TRUE } #' @noRd resolve_comment <- function(model_comments, nm, kind = NULL) { + lookup_nm <- sub(" \\(.*\\)$", "", nm) + resolve_in_kind <- function(kind_name) { comments <- S7::prop(model_comments, tolower(kind_name)) - comment <- comments[[nm]] + comment <- comments[[lookup_nm]] if (!is.null(comment)) { return(comment) } for (cmt in comments) { - if (!is.null(cmt@name) && identical(cmt@name, nm)) { + if (!is.null(cmt@name) && identical(cmt@name, lookup_nm)) { return(cmt) } } @@ -107,7 +87,7 @@ resolve_comment <- function(model_comments, nm, kind = NULL) { if (length(matches) > 1) { rlang::abort(paste0( "Ambiguous parameter name '", - nm, + lookup_nm, "'. Provide kind." )) } diff --git a/R/comments-utils.R b/R/comments-utils.R index 5f1bdf5c..c9c5fab5 100644 --- a/R/comments-utils.R +++ b/R/comments-utils.R @@ -48,3 +48,48 @@ build_comment_tables <- function(comments_list, fields_list, value_resolver) { } tables } + +#' Make a path relative to project root (pharos.toml directory) +#' @noRd +relative_path <- function(path) { + if (is.null(path) || path == "default" || path == "user supplied") { + return(path) + } + tryCatch( + { + config_path <- find_pharos_config_file() + if (grepl("No pharos.toml", config_path)) { + return(path) + } + root <- fs::path_dir(config_path) + as.character(fs::path_rel(path, start = root)) + }, + error = function(e) path + ) +} + +#' Set source paths for comment fields +#' +#' Always initializes the sources attribute to mark object as "initialized". +#' Fields with non-NULL values get source_path; NULL fields get "default". +#' @noRd +set_sources <- function(comment, fields, source_path) { + source_path <- relative_path(source_path) + sources <- list() + for (f in fields) { + val <- S7::prop(comment, f) + if (!is.null(val)) { + sources[[f]] <- source_path + } else { + sources[[f]] <- "default" + } + } + attr(comment, "sources") <- sources + comment +} + +#' @noRd +create_comment_with_sources <- function(constructor, fields, mod_path, ...) { + comment <- constructor(...) + set_sources(comment, fields, mod_path) +} diff --git a/R/extendr-wrappers.R b/R/extendr-wrappers.R index 7d311cd5..a7c11c22 100644 --- a/R/extendr-wrappers.R +++ b/R/extendr-wrappers.R @@ -72,7 +72,7 @@ read_model_from_lst <- function(path) .Call(wrap__read_model_from_lst, path) #' @examples \dontrun{ #' copy_model(from = "model/nonmem/run001.mod", to = "model/nonmem/run002.mod") #' } -copy_model <- function(from, to, overwrite = FALSE, ext_file = NULL, update = 'none', jitter = NULL, jitter_excluded = NULL, seed = NULL, description = NULL, based_on = NULL, no_metadata = FALSE) .Call(wrap__copy_model_wrap, from, to, overwrite, ext_file, update, jitter, jitter_excluded, seed, description, based_on, no_metadata) +copy_model <- function(from, to, overwrite = FALSE, ext_file = NULL, update = 'none', jitter = NULL, jitter_excluded = NULL, seed = NULL, description, based_on = NULL, no_metadata = FALSE) .Call(wrap__copy_model_wrap, from, to, overwrite, ext_file, update, jitter, jitter_excluded, seed, description, based_on, no_metadata) #' Gets model run summary (internal implementation) #' @@ -171,6 +171,23 @@ get_parameters <- function(path, hide_off_diagonal_params = FALSE, only_method = #' @keywords internal get_model_parameter_names <- function(model) .Call(wrap__get_model_parameter_names, model) +#' Build per-parameter comment info from a model object (internal) +#' +#' @param model hyperion_nonmem_model object from read_model() +#' +#' @return list with `thetas`, `omegas`, `sigmas` entries; each is a list of +#' length-2 lists `(coordinate, info)` in numeric coordinate order. +#' @keywords internal +get_model_comment_info <- function(model) .Call(wrap__get_model_comment_info, model) + +#' Canonicalize a parameterization alias to its PascalCase form. +#' +#' @param raw Parameterization alias (e.g. `"EXP"`, `"lognormal"`, `"PROP"`). +#' @return Canonical name (`"LogNormal"`, `"Proportional"`, ...) or `NULL` +#' if `raw` is not a recognized alias. +#' @keywords internal +map_parameterization <- function(raw) .Call(wrap__map_parameterization, raw) + #' Creates a metadata file for a NONMEM model #' #' This function creates a metadata file that stores information about a NONMEM model, diff --git a/R/parameter-info.R b/R/parameter-info.R new file mode 100644 index 00000000..21485808 --- /dev/null +++ b/R/parameter-info.R @@ -0,0 +1,157 @@ +#' Find and read model from .lst file in a directory +#' @noRd +read_model_from_lst_dir <- function(dir_path) { + lst_candidates <- list.files( + dir_path, + pattern = "\\.lst$", + ignore.case = TRUE, + full.names = TRUE + ) + if (length(lst_candidates) == 0) { + rlang::abort(paste0("lst file not found in run directory: ", dir_path)) + } + read_model_from_lst(lst_candidates[1]) +} + +#' Derive output directory from model path and read from .lst file +#' @noRd +read_model_from_lst_path <- function(mod_path) { + mod_path <- from_config_relative(mod_path) + # Derive output directory: run001.mod -> run001/ + base_name <- tools::file_path_sans_ext(basename(mod_path)) + parent_dir <- dirname(mod_path) + output_dir <- file.path(parent_dir, base_name) + + if (!dir.exists(output_dir)) { + rlang::abort(paste0( + "Output directory not found for model: ", + mod_path, + "\nExpected: ", + output_dir + )) + } + + read_model_from_lst_dir(output_dir) +} + +#' Extract all parameter comments from a model as ModelComments object +#' +#' Parses parameter comments and returns structured metadata for theta, omega, +#' and sigma parameters. +#' +#' For model objects sourced from `.mod`/`.ctl` files: +#' - if run status is `"run"`, metadata is read from the corresponding `.lst` +#' - otherwise (`"not_run"`/`"running"`), metadata is read from the model file +#' +#' @param mod A hyperion_nonmem_model object or path to a run output directory +#' containing an .lst file. +#' @param lookup_path Optional path to a TOML lookup file. If provided, fills +#' NULL fields (display, description, unit, parameterization) from the lookup. +#' +#' @return A `ModelComments` object containing theta, omega, and sigma comments. +#' +#' @section Comment Parsing: +#' Comments are parsed by pharos according to the `[nonmem.comments]` section +#' of `pharos.toml`. Set `type = "type1"` for strict structured comments, or +#' `type = "type2"` for a more flexible structured grammar. See pharos +#' documentation for accepted formats. +#' +#' @seealso [get_parameter_transform()], [get_theta_names()], [get_comment()] +#' @export +get_model_parameter_info <- function(mod, lookup_path = NULL) { + if (is.character(mod) && length(mod) == 1) { + mod_path <- normalizePath(mod, mustWork = FALSE) + if (!dir.exists(mod_path)) { + rlang::abort(paste0( + "mod must be a run output directory containing an .lst file: ", + mod_path + )) + } + mod <- read_model_from_lst_dir(mod_path) + } else if (inherits(mod, "hyperion_nonmem_model")) { + mod_path <- attr(mod, "model_source") %||% "unknown" + if (!identical(mod_path, "unknown")) { + mod_path <- from_config_relative(mod_path) + } + # If model was read from .mod/.ctl file: + # - use .lst for completed runs + # - keep model object for not_run/running + if (!grepl("\\.lst$", mod_path, ignore.case = TRUE)) { + run_status <- refresh_run_status(mod) + if (identical(run_status, "run")) { + if (identical(mod_path, "unknown")) { + rlang::abort( + "Cannot locate .lst for completed run: model_source attribute is missing." + ) + } + # Derive output directory from model path (e.g., run001.mod -> run001/) + mod <- read_model_from_lst_path(mod_path) + } else if (!run_status %in% c("not_run", "running")) { + rlang::abort(paste0( + "model run_status must be 'run', 'running', or 'not_run', got: ", + run_status + )) + } + } + } else { + rlang::abort( + "mod must be a hyperion_nonmem_model object or path to a run output directory containing an .lst file" + ) + } + + mod_path <- attr(mod, "model_source") %||% "unknown" + if (!identical(mod_path, "unknown")) { + mod_path <- from_config_relative(mod_path) + } + + info <- get_model_comment_info(mod) + + coerce_field <- function(field, value) { + if (identical(field, "associated_theta")) { + if (length(value) == 0) NULL else as.character(unlist(value)) + } else { + value + } + } + + build_named_list <- function(entries, constructor, fields) { + if (length(entries) == 0) { + return(list()) + } + keys <- vapply(entries, function(e) e[[1]], character(1)) + out <- lapply(entries, function(e) { + data <- e[[2]] + data_args <- lapply(names(data), function(f) coerce_field(f, data[[f]])) + names(data_args) <- names(data) + args <- c( + list( + constructor = constructor, + fields = fields, + mod_path = mod_path, + nonmem_name = e[[1]] + ), + data_args + ) + do.call(create_comment_with_sources, args) + }) + names(out) <- keys + out + } + + theta_comments <- build_named_list(info$thetas, ThetaComment, theta_fields()) + omega_comments <- build_named_list(info$omegas, OmegaComment, omega_fields()) + sigma_comments <- build_named_list(info$sigmas, SigmaComment, sigma_fields()) + + result <- ModelComments( + theta = theta_comments, + omega = omega_comments, + sigma = sigma_comments + ) + + if (!is.null(lookup_path)) { + lookup_path <- normalizePath(lookup_path, mustWork = FALSE) + result <- apply_lookup(result, lookup_path) + } + + result +} diff --git a/inst/extdata/data/derived/PK_Oral_Ex1.csv b/inst/extdata/data/derived/PK_Oral_Ex1.csv deleted file mode 100644 index d92045f8..00000000 --- a/inst/extdata/data/derived/PK_Oral_Ex1.csv +++ /dev/null @@ -1,801 +0,0 @@ -LINE,ID,STUDY,ATFD,NTFD,ATLD,NTLD,DAY,NDAY,AMT,RATE,DUR,CMT,EVID,DVID,ODV,LDV,MDV,BLQ,LLOQ,ROUTE,FREQ,DOSEA,DOSEN,COHORT,PTYPE,SEXF,RACE,AGEBL,AGECBL,WTBL,AEGFRBL,RFCBL,EXCFL -1,1,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,1,19,5,69.2,63,2,1 -2,1,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,1,19,5,69.2,63,2,0 -3,1,1,0.251,0.25,0.251,0.251,1,1,.,.,.,2,0,1,135.44523700374424,4.909,0,0,5,1,1,50,50,1,0,1,1,19,5,69.2,63,2,0 -4,1,1,0.539,0.5,0.539,0.539,1,1,.,.,.,2,0,1,162.78038364546296,5.092,0,0,5,1,1,50,50,1,0,1,1,19,5,69.2,63,2,0 -5,1,1,1.09,1,1.09,1.09,1,1,.,.,.,2,0,1,274.2336868297963,5.614,0,0,5,1,1,50,50,1,0,1,1,19,5,69.2,63,2,0 -6,1,1,4.04,4,4.04,4.04,1,1,.,.,.,2,0,1,157.9651268783753,5.062,0,0,5,1,1,50,50,1,0,1,1,19,5,69.2,63,2,0 -7,1,1,7.909,8,7.909,7.909,1,1,.,.,.,2,0,1,91.09107169399743,4.512,0,0,5,1,1,50,50,1,0,1,1,19,5,69.2,63,2,0 -8,1,1,12.825,12,12.825,12.825,1,1,.,.,.,2,0,1,31.48919877248643,3.45,0,0,5,1,1,50,50,1,0,1,1,19,5,69.2,63,2,0 -9,1,1,22.626,24,22.626,22.626,1,1,.,.,.,2,0,1,15.001380110256893,2.708,0,0,5,1,1,50,50,1,0,1,1,19,5,69.2,63,2,0 -10,1,1,44.077,48,44.077,44.077,2,2,.,.,.,2,0,1,.,.,1,1,5,1,1,50,50,1,0,1,1,19,5,69.2,63,2,0 -11,2,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,3,56,5,79.1,83,2,1 -12,2,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,3,56,5,79.1,83,2,0 -13,2,1,0.265,0.25,0.265,0.265,1,1,.,.,.,2,0,1,97.45319599630928,4.579,0,0,5,1,1,50,50,1,0,1,3,56,5,79.1,83,2,0 -14,2,1,0.451,0.5,0.451,0.451,1,1,.,.,.,2,0,1,230.12964181841207,5.439,0,0,5,1,1,50,50,1,0,1,3,56,5,79.1,83,2,0 -15,2,1,1.035,1,1.035,1.035,1,1,.,.,.,2,0,1,215.75295306533903,5.374,0,0,5,1,1,50,50,1,0,1,3,56,5,79.1,83,2,0 -16,2,1,4.205,4,4.205,4.205,1,1,.,.,.,2,0,1,154.79124313148202,5.042,0,0,5,1,1,50,50,1,0,1,3,56,5,79.1,83,2,0 -17,2,1,8.191,8,8.191,8.191,1,1,.,.,.,2,0,1,106.92951683139773,4.672,0,0,5,1,1,50,50,1,0,1,3,56,5,79.1,83,2,0 -18,2,1,12.748,12,12.748,12.748,1,1,.,.,.,2,0,1,59.571617536167984,4.087,0,0,5,1,1,50,50,1,0,1,3,56,5,79.1,83,2,0 -19,2,1,23.199,24,23.199,23.199,1,1,.,.,.,2,0,1,33.06851120891613,3.499,0,0,5,1,1,50,50,1,0,1,3,56,5,79.1,83,2,0 -20,2,1,44.661,48,44.661,44.661,2,2,.,.,.,2,0,1,11.271104856275693,2.422,0,0,5,1,1,50,50,1,0,1,3,56,5,79.1,83,2,0 -21,3,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,0,1,33,5,110.7,94,1,1 -22,3,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,0,1,33,5,110.7,94,1,0 -23,3,1,0.24,0.25,0.24,0.24,1,1,.,.,.,2,0,1,24.62353109148283,3.204,0,0,5,1,1,50,50,1,0,0,1,33,5,110.7,94,1,0 -24,3,1,0.528,0.5,0.528,0.528,1,1,.,.,.,2,0,1,67.48431907187395,4.212,0,0,5,1,1,50,50,1,0,0,1,33,5,110.7,94,1,0 -25,3,1,1.056,1,1.056,1.056,1,1,.,.,.,2,0,1,72.18967862300104,4.279,0,0,5,1,1,50,50,1,0,0,1,33,5,110.7,94,1,0 -26,3,1,4.348,4,4.348,4.348,1,1,.,.,.,2,0,1,92.36704283920629,4.526,0,0,5,1,1,50,50,1,0,0,1,33,5,110.7,94,1,0 -27,3,1,8.698,8,8.698,8.698,1,1,.,.,.,2,0,1,43.05766164591468,3.763,0,0,5,1,1,50,50,1,0,0,1,33,5,110.7,94,1,0 -28,3,1,11.043,12,11.043,11.043,1,1,.,.,.,2,0,1,21.83841793578263,3.084,0,0,5,1,1,50,50,1,0,0,1,33,5,110.7,94,1,0 -29,3,1,21.666,24,21.666,21.666,1,1,.,.,.,2,0,1,28.91614667097124,3.364,0,0,5,1,1,50,50,1,0,0,1,33,5,110.7,94,1,0 -30,3,1,47.003,48,47.003,47.003,2,2,.,.,.,2,0,1,.,.,1,1,5,1,1,50,50,1,0,0,1,33,5,110.7,94,1,0 -31,4,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,3,60,5,41.9,97,1,1 -32,4,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,3,60,5,41.9,97,1,0 -33,4,1,0.228,0.25,0.228,0.228,1,1,.,.,.,2,0,1,169.6951731610887,5.134,0,0,5,1,1,50,50,1,0,1,3,60,5,41.9,97,1,0 -34,4,1,0.527,0.5,0.527,0.527,1,1,.,.,.,2,0,1,258.225982782318,5.554,0,0,5,1,1,50,50,1,0,1,3,60,5,41.9,97,1,0 -35,4,1,0.931,1,0.931,0.931,1,1,.,.,.,2,0,1,343.8054555025024,5.84,0,0,5,1,1,50,50,1,0,1,3,60,5,41.9,97,1,0 -36,4,1,3.967,4,3.967,3.967,1,1,.,.,.,2,0,1,264.2703488178338,5.577,0,0,5,1,1,50,50,1,0,1,3,60,5,41.9,97,1,0 -37,4,1,7.765,8,7.765,7.765,1,1,.,.,.,2,0,1,193.71969904602912,5.266,0,0,5,1,1,50,50,1,0,1,3,60,5,41.9,97,1,0 -38,4,1,11.455,12,11.455,11.455,1,1,.,.,.,2,0,1,87.72225703058743,4.474,0,0,5,1,1,50,50,1,0,1,3,60,5,41.9,97,1,0 -39,4,1,23.701,24,23.701,23.701,1,1,.,.,.,2,0,1,39.921108893605684,3.687,0,0,5,1,1,50,50,1,0,1,3,60,5,41.9,97,1,0 -40,4,1,44.281,48,44.281,44.281,2,2,.,.,.,2,0,1,6.77985374977073,1.914,0,0,5,1,1,50,50,1,0,1,3,60,5,41.9,97,1,0 -41,5,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,0,1,39,5,47.5,115,1,1 -42,5,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,0,1,39,5,47.5,115,1,0 -43,5,1,0.255,0.25,0.255,0.255,1,1,.,.,.,2,0,1,249.53279459910286,5.52,0,0,5,1,1,50,50,1,0,0,1,39,5,47.5,115,1,0 -44,5,1,0.547,0.5,0.547,0.547,1,1,.,.,.,2,0,1,293.9070261376997,5.683,0,0,5,1,1,50,50,1,0,0,1,39,5,47.5,115,1,0 -45,5,1,1.035,1,1.035,1.035,1,1,.,.,.,2,0,1,490.22226620541164,6.195,0,0,5,1,1,50,50,1,0,0,1,39,5,47.5,115,1,0 -46,5,1,3.81,4,3.81,3.81,1,1,.,.,.,2,0,1,202.44756994084622,5.31,0,0,5,1,1,50,50,1,0,0,1,39,5,47.5,115,1,0 -47,5,1,7.399,8,7.399,7.399,1,1,.,.,.,2,0,1,104.68905735087708,4.651,0,0,5,1,1,50,50,1,0,0,1,39,5,47.5,115,1,0 -48,5,1,11.169,12,11.169,11.169,1,1,.,.,.,2,0,1,141.4274290155354,4.952,0,0,5,1,1,50,50,1,0,0,1,39,5,47.5,115,1,0 -49,5,1,21.694,24,21.694,21.694,1,1,.,.,.,2,0,1,60.800265796533985,4.108,0,0,5,1,1,50,50,1,0,0,1,39,5,47.5,115,1,0 -50,5,1,46.444,48,46.444,46.444,2,2,.,.,.,2,0,1,10.692355648504092,2.37,0,0,5,1,1,50,50,1,0,0,1,39,5,47.5,115,1,0 -51,6,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,3,56,5,71.6,75,2,1 -52,6,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,3,56,5,71.6,75,2,0 -53,6,1,0.253,0.25,0.253,0.253,1,1,.,.,.,2,0,1,94.40039793535624,4.548,0,0,5,1,1,50,50,1,0,1,3,56,5,71.6,75,2,0 -54,6,1,0.459,0.5,0.459,0.459,1,1,.,.,.,2,0,1,203.10586802391276,5.314,0,0,5,1,1,50,50,1,0,1,3,56,5,71.6,75,2,0 -55,6,1,1.002,1,1.002,1.002,1,1,.,.,.,2,0,1,318.2833447860227,5.763,0,0,5,1,1,50,50,1,0,1,3,56,5,71.6,75,2,0 -56,6,1,4.203,4,4.203,4.203,1,1,.,.,.,2,0,1,290.60996160859804,5.672,0,0,5,1,1,50,50,1,0,1,3,56,5,71.6,75,2,0 -57,6,1,7.751,8,7.751,7.751,1,1,.,.,.,2,0,1,96.80287654224922,4.573,0,0,5,1,1,50,50,1,0,1,3,56,5,71.6,75,2,0 -58,6,1,12.722,12,12.722,12.722,1,1,.,.,.,2,0,1,46.77188217118976,3.845,0,0,5,1,1,50,50,1,0,1,3,56,5,71.6,75,2,0 -59,6,1,24.606,24,24.606,24.606,2,1,.,.,.,2,0,1,56.46229354509565,4.034,0,0,5,1,1,50,50,1,0,1,3,56,5,71.6,75,2,0 -60,6,1,51.202,48,51.202,51.202,3,2,.,.,.,2,0,1,12.935218528455787,2.56,0,0,5,1,1,50,50,1,0,1,3,56,5,71.6,75,2,0 -61,7,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,3,29,5,66.4,62,2,1 -62,7,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,3,29,5,66.4,62,2,0 -63,7,1,0.262,0.25,0.262,0.262,1,1,.,.,.,2,0,1,135.4737080194625,4.909,0,0,5,1,1,50,50,1,0,1,3,29,5,66.4,62,2,0 -64,7,1,0.499,0.5,0.499,0.499,1,1,.,.,.,2,0,1,184.77808800181336,5.219,0,0,5,1,1,50,50,1,0,1,3,29,5,66.4,62,2,0 -65,7,1,1.019,1,1.019,1.019,1,1,.,.,.,2,0,1,218.6830683928352,5.388,0,0,5,1,1,50,50,1,0,1,3,29,5,66.4,62,2,0 -66,7,1,3.658,4,3.658,3.658,1,1,.,.,.,2,0,1,269.5928245332529,5.597,0,0,5,1,1,50,50,1,0,1,3,29,5,66.4,62,2,0 -67,7,1,8.181,8,8.181,8.181,1,1,.,.,.,2,0,1,154.59442515544734,5.041,0,0,5,1,1,50,50,1,0,1,3,29,5,66.4,62,2,0 -68,7,1,10.963,12,10.963,10.963,1,1,.,.,.,2,0,1,129.4545159104613,4.863,0,0,5,1,1,50,50,1,0,1,3,29,5,66.4,62,2,0 -69,7,1,23.188,24,23.188,23.188,1,1,.,.,.,2,0,1,64.33189339925366,4.164,0,0,5,1,1,50,50,1,0,1,3,29,5,66.4,62,2,0 -70,7,1,44.457,48,44.457,44.457,2,2,.,.,.,2,0,1,18.656472147539514,2.926,0,0,5,1,1,50,50,1,0,1,3,29,5,66.4,62,2,0 -71,8,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,1,49,5,84.7,116,1,1 -72,8,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,1,49,5,84.7,116,1,0 -73,8,1,0.252,0.25,0.252,0.252,1,1,.,.,.,2,0,1,100.28365397178807,4.608,0,0,5,1,1,50,50,1,0,1,1,49,5,84.7,116,1,0 -74,8,1,0.536,0.5,0.536,0.536,1,1,.,.,.,2,0,1,141.05269221146096,4.949,0,0,5,1,1,50,50,1,0,1,1,49,5,84.7,116,1,0 -75,8,1,0.952,1,0.952,0.952,1,1,.,.,.,2,0,1,199.3914846938247,5.295,0,0,5,1,1,50,50,1,0,1,1,49,5,84.7,116,1,0 -76,8,1,4.093,4,4.093,4.093,1,1,.,.,.,2,0,1,227.61449941839328,5.428,0,0,5,1,1,50,50,1,0,1,1,49,5,84.7,116,1,0 -77,8,1,8.18,8,8.18,8.18,1,1,.,.,.,2,0,1,79.16009593111572,4.371,0,0,5,1,1,50,50,1,0,1,1,49,5,84.7,116,1,0 -78,8,1,11.742,12,11.742,11.742,1,1,.,.,.,2,0,1,65.32755678251588,4.179,0,0,5,1,1,50,50,1,0,1,1,49,5,84.7,116,1,0 -79,8,1,25.634,24,25.634,25.634,2,1,.,.,.,2,0,1,27.734669316625514,3.323,0,0,5,1,1,50,50,1,0,1,1,49,5,84.7,116,1,0 -80,8,1,46.703,48,46.703,46.703,2,2,.,.,.,2,0,1,6.5804820648334665,1.884,0,0,5,1,1,50,50,1,0,1,1,49,5,84.7,116,1,0 -81,9,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,3,26,5,88.1,82,2,1 -82,9,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,3,26,5,88.1,82,2,0 -83,9,1,0.271,0.25,0.271,0.271,1,1,.,.,.,2,0,1,55.92177081314453,4.024,0,0,5,1,1,50,50,1,0,1,3,26,5,88.1,82,2,0 -84,9,1,0.538,0.5,0.538,0.538,1,1,.,.,.,2,0,1,178.77919287456174,5.186,0,0,5,1,1,50,50,1,0,1,3,26,5,88.1,82,2,0 -85,9,1,1.071,1,1.071,1.071,1,1,.,.,.,2,0,1,202.00438237197667,5.308,0,0,5,1,1,50,50,1,0,1,3,26,5,88.1,82,2,0 -86,9,1,3.967,4,3.967,3.967,1,1,.,.,.,2,0,1,173.26650922002892,5.155,0,0,5,1,1,50,50,1,0,1,3,26,5,88.1,82,2,0 -87,9,1,8.777,8,8.777,8.777,1,1,.,.,.,2,0,1,88.45712081437316,4.483,0,0,5,1,1,50,50,1,0,1,3,26,5,88.1,82,2,0 -88,9,1,13.095,12,13.095,13.095,1,1,.,.,.,2,0,1,73.79496848929563,4.301,0,0,5,1,1,50,50,1,0,1,3,26,5,88.1,82,2,0 -89,9,1,23.735,24,23.735,23.735,1,1,.,.,.,2,0,1,62.127232142389644,4.129,0,0,5,1,1,50,50,1,0,1,3,26,5,88.1,82,2,0 -90,9,1,52.001,48,52.001,52.001,3,2,.,.,.,2,0,1,16.85654187006987,2.825,0,0,5,1,1,50,50,1,0,1,3,26,5,88.1,82,2,0 -91,10,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,3,27,5,94.2,95,1,1 -92,10,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,3,27,5,94.2,95,1,0 -93,10,1,0.254,0.25,0.254,0.254,1,1,.,.,.,2,0,1,62.70468207315369,4.138,0,0,5,1,1,50,50,1,0,1,3,27,5,94.2,95,1,0 -94,10,1,0.466,0.5,0.466,0.466,1,1,.,.,.,2,0,1,108.89761745387702,4.69,0,0,5,1,1,50,50,1,0,1,3,27,5,94.2,95,1,0 -95,10,1,1.002,1,1.002,1.002,1,1,.,.,.,2,0,1,185.15065197398172,5.221,0,0,5,1,1,50,50,1,0,1,3,27,5,94.2,95,1,0 -96,10,1,3.928,4,3.928,3.928,1,1,.,.,.,2,0,1,195.6364618026376,5.276,0,0,5,1,1,50,50,1,0,1,3,27,5,94.2,95,1,0 -97,10,1,7.848,8,7.848,7.848,1,1,.,.,.,2,0,1,188.06280516669372,5.237,0,0,5,1,1,50,50,1,0,1,3,27,5,94.2,95,1,0 -98,10,1,11.936,12,11.936,11.936,1,1,.,.,.,2,0,1,82.40375610664775,4.412,0,0,5,1,1,50,50,1,0,1,3,27,5,94.2,95,1,0 -99,10,1,26.052,24,26.052,26.052,2,1,.,.,.,2,0,1,34.7389085775917,3.548,0,0,5,1,1,50,50,1,0,1,3,27,5,94.2,95,1,0 -100,10,1,51.715,48,51.715,51.715,3,2,.,.,.,2,0,1,18.226268866909013,2.903,0,0,5,1,1,50,50,1,0,1,3,27,5,94.2,95,1,0 -101,11,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,0,1,43,5,50.6,109,1,1 -102,11,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,0,1,43,5,50.6,109,1,0 -103,11,1,0.245,0.25,0.245,0.245,1,1,.,.,.,2,0,1,105.19001516194388,4.656,0,0,5,1,1,50,50,1,0,0,1,43,5,50.6,109,1,0 -104,11,1,0.538,0.5,0.538,0.538,1,1,.,.,.,2,0,1,168.28684337711428,5.126,0,0,5,1,1,50,50,1,0,0,1,43,5,50.6,109,1,0 -105,11,1,0.925,1,0.925,0.925,1,1,.,.,.,2,0,1,324.1060870362852,5.781,0,0,5,1,1,50,50,1,0,0,1,43,5,50.6,109,1,0 -106,11,1,4.089,4,4.089,4.089,1,1,.,.,.,2,0,1,231.8231074063792,5.446,0,0,5,1,1,50,50,1,0,0,1,43,5,50.6,109,1,0 -107,11,1,7.507,8,7.507,7.507,1,1,.,.,.,2,0,1,123.41807808522007,4.816,0,0,5,1,1,50,50,1,0,0,1,43,5,50.6,109,1,0 -108,11,1,12.29,12,12.29,12.29,1,1,.,.,.,2,0,1,91.97991358767749,4.522,0,0,5,1,1,50,50,1,0,0,1,43,5,50.6,109,1,0 -109,11,1,25.592,24,25.592,25.592,2,1,.,.,.,2,0,1,22.631899009411562,3.119,0,0,5,1,1,50,50,1,0,0,1,43,5,50.6,109,1,0 -110,11,1,47.142,48,47.142,47.142,2,2,.,.,.,2,0,1,.,.,1,1,5,1,1,50,50,1,0,0,1,43,5,50.6,109,1,0 -111,12,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,3,38,5,74.6,89,2,1 -112,12,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,3,38,5,74.6,89,2,0 -113,12,1,0.272,0.25,0.272,0.272,1,1,.,.,.,2,0,1,89.80210143555645,4.498,0,0,5,1,1,50,50,1,0,1,3,38,5,74.6,89,2,0 -114,12,1,0.479,0.5,0.479,0.479,1,1,.,.,.,2,0,1,149.78718107024633,5.009,0,0,5,1,1,50,50,1,0,1,3,38,5,74.6,89,2,0 -115,12,1,1.074,1,1.074,1.074,1,1,.,.,.,2,0,1,223.0873178541168,5.408,0,0,5,1,1,50,50,1,0,1,3,38,5,74.6,89,2,0 -116,12,1,4.026,4,4.026,4.026,1,1,.,.,.,2,0,1,201.5750956395063,5.306,0,0,5,1,1,50,50,1,0,1,3,38,5,74.6,89,2,0 -117,12,1,7.663,8,7.663,7.663,1,1,.,.,.,2,0,1,99.6120343556312,4.601,0,0,5,1,1,50,50,1,0,1,3,38,5,74.6,89,2,0 -118,12,1,11.966,12,11.966,11.966,1,1,.,.,.,2,0,1,101.20921325003067,4.617,0,0,5,1,1,50,50,1,0,1,3,38,5,74.6,89,2,0 -119,12,1,23.192,24,23.192,23.192,1,1,.,.,.,2,0,1,53.89477012311715,3.987,0,0,5,1,1,50,50,1,0,1,3,38,5,74.6,89,2,0 -120,12,1,52.646,48,52.646,52.646,3,2,.,.,.,2,0,1,6.81006024388014,1.918,0,0,5,1,1,50,50,1,0,1,3,38,5,74.6,89,2,0 -121,13,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,3,40,5,80,73,2,1 -122,13,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,3,40,5,80,73,2,0 -123,13,1,0.245,0.25,0.245,0.245,1,1,.,.,.,2,0,1,83.43894309203456,4.424,0,0,5,1,1,50,50,1,0,1,3,40,5,80,73,2,0 -124,13,1,0.468,0.5,0.468,0.468,1,1,.,.,.,2,0,1,152.993972858459,5.03,0,0,5,1,1,50,50,1,0,1,3,40,5,80,73,2,0 -125,13,1,0.918,1,0.918,0.918,1,1,.,.,.,2,0,1,229.83436240261514,5.437,0,0,5,1,1,50,50,1,0,1,3,40,5,80,73,2,0 -126,13,1,4.159,4,4.159,4.159,1,1,.,.,.,2,0,1,138.69508050143315,4.932,0,0,5,1,1,50,50,1,0,1,3,40,5,80,73,2,0 -127,13,1,8.229,8,8.229,8.229,1,1,.,.,.,2,0,1,105.52481658119962,4.659,0,0,5,1,1,50,50,1,0,1,3,40,5,80,73,2,0 -128,13,1,12.449,12,12.449,12.449,1,1,.,.,.,2,0,1,82.69099954281738,4.415,0,0,5,1,1,50,50,1,0,1,3,40,5,80,73,2,0 -129,13,1,23.564,24,23.564,23.564,1,1,.,.,.,2,0,1,45.429328987267105,3.816,0,0,5,1,1,50,50,1,0,1,3,40,5,80,73,2,0 -130,13,1,52.558,48,52.558,52.558,3,2,.,.,.,2,0,1,16.92462070175668,2.829,0,0,5,1,1,50,50,1,0,1,3,40,5,80,73,2,0 -131,14,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,0,1,55,5,58.3,82,2,1 -132,14,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,0,1,55,5,58.3,82,2,0 -133,14,1,0.259,0.25,0.259,0.259,1,1,.,.,.,2,0,1,85.36653666847653,4.447,0,0,5,1,1,50,50,1,0,0,1,55,5,58.3,82,2,0 -134,14,1,0.463,0.5,0.463,0.463,1,1,.,.,.,2,0,1,82.3398004759733,4.411,0,0,5,1,1,50,50,1,0,0,1,55,5,58.3,82,2,0 -135,14,1,1.04,1,1.04,1.04,1,1,.,.,.,2,0,1,204.46690489883147,5.32,0,0,5,1,1,50,50,1,0,0,1,55,5,58.3,82,2,0 -136,14,1,4.239,4,4.239,4.239,1,1,.,.,.,2,0,1,194.76042695170875,5.272,0,0,5,1,1,50,50,1,0,0,1,55,5,58.3,82,2,0 -137,14,1,7.206,8,7.206,7.206,1,1,.,.,.,2,0,1,142.99341877974857,4.963,0,0,5,1,1,50,50,1,0,0,1,55,5,58.3,82,2,0 -138,14,1,12.395,12,12.395,12.395,1,1,.,.,.,2,0,1,85.56372644655724,4.449,0,0,5,1,1,50,50,1,0,0,1,55,5,58.3,82,2,0 -139,14,1,23.237,24,23.237,23.237,1,1,.,.,.,2,0,1,29.642554300031655,3.389,0,0,5,1,1,50,50,1,0,0,1,55,5,58.3,82,2,0 -140,14,1,45.973,48,45.973,45.973,2,2,.,.,.,2,0,1,6.010796832004558,1.794,0,0,5,1,1,50,50,1,0,0,1,55,5,58.3,82,2,0 -141,15,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,3,59,5,91.9,108,1,1 -142,15,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,3,59,5,91.9,108,1,0 -143,15,1,0.236,0.25,0.236,0.236,1,1,.,.,.,2,0,1,72.2221702863214,4.28,0,0,5,1,1,50,50,1,0,1,3,59,5,91.9,108,1,0 -144,15,1,0.502,0.5,0.502,0.502,1,1,.,.,.,2,0,1,145.8125529365367,4.982,0,0,5,1,1,50,50,1,0,1,3,59,5,91.9,108,1,0 -145,15,1,0.958,1,0.958,0.958,1,1,.,.,.,2,0,1,245.45303661440187,5.503,0,0,5,1,1,50,50,1,0,1,3,59,5,91.9,108,1,0 -146,15,1,3.879,4,3.879,3.879,1,1,.,.,.,2,0,1,189.70425402824299,5.245,0,0,5,1,1,50,50,1,0,1,3,59,5,91.9,108,1,0 -147,15,1,7.258,8,7.258,7.258,1,1,.,.,.,2,0,1,178.6140994407548,5.185,0,0,5,1,1,50,50,1,0,1,3,59,5,91.9,108,1,0 -148,15,1,12.106,12,12.106,12.106,1,1,.,.,.,2,0,1,73.85645867201875,4.302,0,0,5,1,1,50,50,1,0,1,3,59,5,91.9,108,1,0 -149,15,1,23.431,24,23.431,23.431,1,1,.,.,.,2,0,1,58.5182186455088,4.069,0,0,5,1,1,50,50,1,0,1,3,59,5,91.9,108,1,0 -150,15,1,50.025,48,50.025,50.025,3,2,.,.,.,2,0,1,17.587436942518334,2.867,0,0,5,1,1,50,50,1,0,1,3,59,5,91.9,108,1,0 -151,16,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,3,32,5,57.9,106,1,1 -152,16,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,3,32,5,57.9,106,1,0 -153,16,1,0.245,0.25,0.245,0.245,1,1,.,.,.,2,0,1,146.0809549899598,4.984,0,0,5,1,1,50,50,1,0,1,3,32,5,57.9,106,1,0 -154,16,1,0.456,0.5,0.456,0.456,1,1,.,.,.,2,0,1,248.67788229455675,5.516,0,0,5,1,1,50,50,1,0,1,3,32,5,57.9,106,1,0 -155,16,1,1.061,1,1.061,1.061,1,1,.,.,.,2,0,1,356.82911831523467,5.877,0,0,5,1,1,50,50,1,0,1,3,32,5,57.9,106,1,0 -156,16,1,3.706,4,3.706,3.706,1,1,.,.,.,2,0,1,181.47615746102215,5.201,0,0,5,1,1,50,50,1,0,1,3,32,5,57.9,106,1,0 -157,16,1,8.25,8,8.25,8.25,1,1,.,.,.,2,0,1,191.74983411936978,5.256,0,0,5,1,1,50,50,1,0,1,3,32,5,57.9,106,1,0 -158,16,1,12.325,12,12.325,12.325,1,1,.,.,.,2,0,1,152.6125825987005,5.028,0,0,5,1,1,50,50,1,0,1,3,32,5,57.9,106,1,0 -159,16,1,22.16,24,22.16,22.16,1,1,.,.,.,2,0,1,51.689080868684464,3.945,0,0,5,1,1,50,50,1,0,1,3,32,5,57.9,106,1,0 -160,16,1,49.707,48,49.707,49.707,3,2,.,.,.,2,0,1,10.465110466955615,2.348,0,0,5,1,1,50,50,1,0,1,3,32,5,57.9,106,1,0 -161,17,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,0,1,23,5,62.7,119,1,1 -162,17,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,0,1,23,5,62.7,119,1,0 -163,17,1,0.251,0.25,0.251,0.251,1,1,.,.,.,2,0,1,142.03067351163958,4.956,0,0,5,1,1,50,50,1,0,0,1,23,5,62.7,119,1,0 -164,17,1,0.522,0.5,0.522,0.522,1,1,.,.,.,2,0,1,187.9079244478451,5.236,0,0,5,1,1,50,50,1,0,0,1,23,5,62.7,119,1,0 -165,17,1,1.051,1,1.051,1.051,1,1,.,.,.,2,0,1,319.1262156050448,5.766,0,0,5,1,1,50,50,1,0,0,1,23,5,62.7,119,1,0 -166,17,1,3.657,4,3.657,3.657,1,1,.,.,.,2,0,1,172.90508940988434,5.153,0,0,5,1,1,50,50,1,0,0,1,23,5,62.7,119,1,0 -167,17,1,8.164,8,8.164,8.164,1,1,.,.,.,2,0,1,85.9578520260203,4.454,0,0,5,1,1,50,50,1,0,0,1,23,5,62.7,119,1,0 -168,17,1,10.92,12,10.92,10.92,1,1,.,.,.,2,0,1,43.746953996386196,3.778,0,0,5,1,1,50,50,1,0,0,1,23,5,62.7,119,1,0 -169,17,1,24.093,24,24.093,24.093,2,1,.,.,.,2,0,1,24.0419862220063,3.18,0,0,5,1,1,50,50,1,0,0,1,23,5,62.7,119,1,0 -170,17,1,46.256,48,46.256,46.256,2,2,.,.,.,2,0,1,.,.,1,1,5,1,1,50,50,1,0,0,1,23,5,62.7,119,1,0 -171,18,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,0,1,21,5,65.6,88,2,1 -172,18,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,0,1,21,5,65.6,88,2,0 -173,18,1,0.248,0.25,0.248,0.248,1,1,.,.,.,2,0,1,63.547226666011206,4.152,0,0,5,1,1,50,50,1,0,0,1,21,5,65.6,88,2,0 -174,18,1,0.492,0.5,0.492,0.492,1,1,.,.,.,2,0,1,76.10211318642305,4.332,0,0,5,1,1,50,50,1,0,0,1,21,5,65.6,88,2,0 -175,18,1,0.953,1,0.953,0.953,1,1,.,.,.,2,0,1,127.96102253972899,4.852,0,0,5,1,1,50,50,1,0,0,1,21,5,65.6,88,2,0 -176,18,1,3.894,4,3.894,3.894,1,1,.,.,.,2,0,1,146.0356260475136,4.984,0,0,5,1,1,50,50,1,0,0,1,21,5,65.6,88,2,0 -177,18,1,7.975,8,7.975,7.975,1,1,.,.,.,2,0,1,85.07102175609896,4.443,0,0,5,1,1,50,50,1,0,0,1,21,5,65.6,88,2,0 -178,18,1,11.66,12,11.66,11.66,1,1,.,.,.,2,0,1,67.02881309796206,4.205,0,0,5,1,1,50,50,1,0,0,1,21,5,65.6,88,2,0 -179,18,1,22.861,24,22.861,22.861,1,1,.,.,.,2,0,1,19.305289030884524,2.96,0,0,5,1,1,50,50,1,0,0,1,21,5,65.6,88,2,0 -180,18,1,51.323,48,51.323,51.323,3,2,.,.,.,2,0,1,.,.,1,1,5,1,1,50,50,1,0,0,1,21,5,65.6,88,2,0 -181,19,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,0,1,30,5,67.5,78,2,1 -182,19,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,0,1,30,5,67.5,78,2,0 -183,19,1,0.243,0.25,0.243,0.243,1,1,.,.,.,2,0,1,52.32994461808845,3.958,0,0,5,1,1,50,50,1,0,0,1,30,5,67.5,78,2,0 -184,19,1,0.492,0.5,0.492,0.492,1,1,.,.,.,2,0,1,102.23023201300768,4.627,0,0,5,1,1,50,50,1,0,0,1,30,5,67.5,78,2,0 -185,19,1,1.094,1,1.094,1.094,1,1,.,.,.,2,0,1,157.69795914362115,5.061,0,0,5,1,1,50,50,1,0,0,1,30,5,67.5,78,2,0 -186,19,1,4.219,4,4.219,4.219,1,1,.,.,.,2,0,1,204.3837705584912,5.32,0,0,5,1,1,50,50,1,0,0,1,30,5,67.5,78,2,0 -187,19,1,7.474,8,7.474,7.474,1,1,.,.,.,2,0,1,130.6893154068886,4.873,0,0,5,1,1,50,50,1,0,0,1,30,5,67.5,78,2,0 -188,19,1,11.87,12,11.87,11.87,1,1,.,.,.,2,0,1,43.83466017740129,3.78,0,0,5,1,1,50,50,1,0,0,1,30,5,67.5,78,2,0 -189,19,1,23.167,24,23.167,23.167,1,1,.,.,.,2,0,1,49.75309931078222,3.907,0,0,5,1,1,50,50,1,0,0,1,30,5,67.5,78,2,0 -190,19,1,49.134,48,49.134,49.134,3,2,.,.,.,2,0,1,10.912049044214108,2.39,0,0,5,1,1,50,50,1,0,0,1,30,5,67.5,78,2,0 -191,20,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,50,50,1,0,1,3,26,5,56.3,115,1,1 -192,20,1,0,0,0,0,1,1,50,.,.,1,1,0,.,.,1,0,5,1,1,50,50,1,0,1,3,26,5,56.3,115,1,0 -193,20,1,0.228,0.25,0.228,0.228,1,1,.,.,.,2,0,1,126.5020318046857,4.84,0,0,5,1,1,50,50,1,0,1,3,26,5,56.3,115,1,0 -194,20,1,0.498,0.5,0.498,0.498,1,1,.,.,.,2,0,1,218.22018597644612,5.386,0,0,5,1,1,50,50,1,0,1,3,26,5,56.3,115,1,0 -195,20,1,0.993,1,0.993,0.993,1,1,.,.,.,2,0,1,267.6697423227123,5.59,0,0,5,1,1,50,50,1,0,1,3,26,5,56.3,115,1,0 -196,20,1,3.853,4,3.853,3.853,1,1,.,.,.,2,0,1,239.16161296190822,5.477,0,0,5,1,1,50,50,1,0,1,3,26,5,56.3,115,1,0 -197,20,1,8.051,8,8.051,8.051,1,1,.,.,.,2,0,1,164.85086385207168,5.105,0,0,5,1,1,50,50,1,0,1,3,26,5,56.3,115,1,0 -198,20,1,11.317,12,11.317,11.317,1,1,.,.,.,2,0,1,121.27169586086534,4.798,0,0,5,1,1,50,50,1,0,1,3,26,5,56.3,115,1,0 -199,20,1,23.219,24,23.219,23.219,1,1,.,.,.,2,0,1,37.72747226980194,3.63,0,0,5,1,1,50,50,1,0,1,3,26,5,56.3,115,1,0 -200,20,1,46.793,48,46.793,46.793,2,2,.,.,.,2,0,1,10.55656806084108,2.357,0,0,5,1,1,50,50,1,0,1,3,26,5,56.3,115,1,0 -201,21,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,1,3,49,5,86,112,1,1 -202,21,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,1,3,49,5,86,112,1,0 -203,21,1,0.253,0.25,0.253,0.253,1,1,.,.,.,2,0,1,222.20388527834575,5.404,0,0,5,1,1,100,100,2,0,1,3,49,5,86,112,1,0 -204,21,1,0.524,0.5,0.524,0.524,1,1,.,.,.,2,0,1,398.25758297732244,5.987,0,0,5,1,1,100,100,2,0,1,3,49,5,86,112,1,0 -205,21,1,0.958,1,0.958,0.958,1,1,.,.,.,2,0,1,518.2403345077166,6.25,0,0,5,1,1,100,100,2,0,1,3,49,5,86,112,1,0 -206,21,1,4.203,4,4.203,4.203,1,1,.,.,.,2,0,1,348.63694073274286,5.854,0,0,5,1,1,100,100,2,0,1,3,49,5,86,112,1,0 -207,21,1,7.365,8,7.365,7.365,1,1,.,.,.,2,0,1,176.74557193919847,5.175,0,0,5,1,1,100,100,2,0,1,3,49,5,86,112,1,0 -208,21,1,12.031,12,12.031,12.031,1,1,.,.,.,2,0,1,160.80803303860978,5.08,0,0,5,1,1,100,100,2,0,1,3,49,5,86,112,1,0 -209,21,1,24.323,24,24.323,24.323,2,1,.,.,.,2,0,1,67.33980871753079,4.21,0,0,5,1,1,100,100,2,0,1,3,49,5,86,112,1,0 -210,21,1,47.503,48,47.503,47.503,2,2,.,.,.,2,0,1,24.282924658464673,3.19,0,0,5,1,1,100,100,2,0,1,3,49,5,86,112,1,0 -211,22,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,0,1,19,5,77.6,88,2,1 -212,22,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,0,1,19,5,77.6,88,2,0 -213,22,1,0.264,0.25,0.264,0.264,1,1,.,.,.,2,0,1,113.56922512962268,4.732,0,0,5,1,1,100,100,2,0,0,1,19,5,77.6,88,2,0 -214,22,1,0.459,0.5,0.459,0.459,1,1,.,.,.,2,0,1,134.64147802024502,4.903,0,0,5,1,1,100,100,2,0,0,1,19,5,77.6,88,2,0 -215,22,1,0.971,1,0.971,0.971,1,1,.,.,.,2,0,1,308.0805834282529,5.73,0,0,5,1,1,100,100,2,0,0,1,19,5,77.6,88,2,0 -216,22,1,3.686,4,3.686,3.686,1,1,.,.,.,2,0,1,245.73193003290982,5.504,0,0,5,1,1,100,100,2,0,0,1,19,5,77.6,88,2,0 -217,22,1,7.327,8,7.327,7.327,1,1,.,.,.,2,0,1,199.30078032618894,5.295,0,0,5,1,1,100,100,2,0,0,1,19,5,77.6,88,2,0 -218,22,1,12.042,12,12.042,12.042,1,1,.,.,.,2,0,1,73.38640214868025,4.296,0,0,5,1,1,100,100,2,0,0,1,19,5,77.6,88,2,0 -219,22,1,21.601,24,21.601,21.601,1,1,.,.,.,2,0,1,39.890315458890676,3.686,0,0,5,1,1,100,100,2,0,0,1,19,5,77.6,88,2,0 -220,22,1,44.514,48,44.514,44.514,2,2,.,.,.,2,0,1,8.13083127863289,2.096,0,0,5,1,1,100,100,2,0,0,1,19,5,77.6,88,2,0 -221,23,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,1,3,54,5,52.7,60,2,1 -222,23,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,1,3,54,5,52.7,60,2,0 -223,23,1,0.254,0.25,0.254,0.254,1,1,.,.,.,2,0,1,186.23930136729774,5.227,0,0,5,1,1,100,100,2,0,1,3,54,5,52.7,60,2,0 -224,23,1,0.518,0.5,0.518,0.518,1,1,.,.,.,2,0,1,510.8848940320924,6.236,0,0,5,1,1,100,100,2,0,1,3,54,5,52.7,60,2,0 -225,23,1,1.077,1,1.077,1.077,1,1,.,.,.,2,0,1,427.1187622139237,6.057,0,0,5,1,1,100,100,2,0,1,3,54,5,52.7,60,2,0 -226,23,1,4.216,4,4.216,4.216,1,1,.,.,.,2,0,1,614.2355913036895,6.42,0,0,5,1,1,100,100,2,0,1,3,54,5,52.7,60,2,0 -227,23,1,7.564,8,7.564,7.564,1,1,.,.,.,2,0,1,561.9300237067965,6.331,0,0,5,1,1,100,100,2,0,1,3,54,5,52.7,60,2,0 -228,23,1,10.834,12,10.834,10.834,1,1,.,.,.,2,0,1,316.43371686943203,5.757,0,0,5,1,1,100,100,2,0,1,3,54,5,52.7,60,2,0 -229,23,1,25.201,24,25.201,25.201,2,1,.,.,.,2,0,1,119.67245706493227,4.785,0,0,5,1,1,100,100,2,0,1,3,54,5,52.7,60,2,0 -230,23,1,51.172,48,51.172,51.172,3,2,.,.,.,2,0,1,26.37359389340642,3.272,0,0,5,1,1,100,100,2,0,1,3,54,5,52.7,60,2,0 -231,24,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,0,1,37,5,75,74,2,1 -232,24,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,0,1,37,5,75,74,2,0 -233,24,1,0.249,0.25,0.249,0.249,1,1,.,.,.,2,0,1,177.10528460730433,5.177,0,0,5,1,1,100,100,2,0,0,1,37,5,75,74,2,0 -234,24,1,0.461,0.5,0.461,0.461,1,1,.,.,.,2,0,1,372.62432730775413,5.921,0,0,5,1,1,100,100,2,0,0,1,37,5,75,74,2,0 -235,24,1,1.037,1,1.037,1.037,1,1,.,.,.,2,0,1,559.2484081413529,6.327,0,0,5,1,1,100,100,2,0,0,1,37,5,75,74,2,0 -236,24,1,3.606,4,3.606,3.606,1,1,.,.,.,2,0,1,369.18356207841725,5.911,0,0,5,1,1,100,100,2,0,0,1,37,5,75,74,2,0 -237,24,1,7.785,8,7.785,7.785,1,1,.,.,.,2,0,1,168.99172047389044,5.13,0,0,5,1,1,100,100,2,0,0,1,37,5,75,74,2,0 -238,24,1,12.774,12,12.774,12.774,1,1,.,.,.,2,0,1,60.90699672033179,4.109,0,0,5,1,1,100,100,2,0,0,1,37,5,75,74,2,0 -239,24,1,21.798,24,21.798,21.798,1,1,.,.,.,2,0,1,66.75234319402962,4.201,0,0,5,1,1,100,100,2,0,0,1,37,5,75,74,2,0 -240,24,1,43.678,48,43.678,43.678,2,2,.,.,.,2,0,1,16.993578396296172,2.833,0,0,5,1,1,100,100,2,0,0,1,37,5,75,74,2,0 -241,25,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,0,1,46,5,65.5,117,1,1 -242,25,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,0,1,46,5,65.5,117,1,0 -243,25,1,0.271,0.25,0.271,0.271,1,1,.,.,.,2,0,1,150.58974977915426,5.015,0,0,5,1,1,100,100,2,0,0,1,46,5,65.5,117,1,0 -244,25,1,0.471,0.5,0.471,0.471,1,1,.,.,.,2,0,1,184.75505229278053,5.219,0,0,5,1,1,100,100,2,0,0,1,46,5,65.5,117,1,0 -245,25,1,0.971,1,0.971,0.971,1,1,.,.,.,2,0,1,463.9218796878544,6.14,0,0,5,1,1,100,100,2,0,0,1,46,5,65.5,117,1,0 -246,25,1,4.309,4,4.309,4.309,1,1,.,.,.,2,0,1,307.5533109805224,5.729,0,0,5,1,1,100,100,2,0,0,1,46,5,65.5,117,1,0 -247,25,1,8.611,8,8.611,8.611,1,1,.,.,.,2,0,1,166.96362286652396,5.118,0,0,5,1,1,100,100,2,0,0,1,46,5,65.5,117,1,0 -248,25,1,11.404,12,11.404,11.404,1,1,.,.,.,2,0,1,90.13967577374842,4.501,0,0,5,1,1,100,100,2,0,0,1,46,5,65.5,117,1,0 -249,25,1,23.08,24,23.08,23.08,1,1,.,.,.,2,0,1,37.07870656317447,3.613,0,0,5,1,1,100,100,2,0,0,1,46,5,65.5,117,1,0 -250,25,1,44.604,48,44.604,44.604,2,2,.,.,.,2,0,1,5.5919126397116825,1.721,0,0,5,1,1,100,100,2,0,0,1,46,5,65.5,117,1,0 -251,26,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,1,3,24,5,68.4,120,1,1 -252,26,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,1,3,24,5,68.4,120,1,0 -253,26,1,0.234,0.25,0.234,0.234,1,1,.,.,.,2,0,1,197.5755036051143,5.286,0,0,5,1,1,100,100,2,0,1,3,24,5,68.4,120,1,0 -254,26,1,0.452,0.5,0.452,0.452,1,1,.,.,.,2,0,1,117.84540397640403,4.769,0,0,5,1,1,100,100,2,0,1,3,24,5,68.4,120,1,0 -255,26,1,1.081,1,1.081,1.081,1,1,.,.,.,2,0,1,324.1776215337058,5.781,0,0,5,1,1,100,100,2,0,1,3,24,5,68.4,120,1,0 -256,26,1,4.145,4,4.145,4.145,1,1,.,.,.,2,0,1,449.5119850783137,6.108,0,0,5,1,1,100,100,2,0,1,3,24,5,68.4,120,1,0 -257,26,1,7.358,8,7.358,7.358,1,1,.,.,.,2,0,1,195.77001223162944,5.277,0,0,5,1,1,100,100,2,0,1,3,24,5,68.4,120,1,0 -258,26,1,10.958,12,10.958,10.958,1,1,.,.,.,2,0,1,211.63561405874458,5.355,0,0,5,1,1,100,100,2,0,1,3,24,5,68.4,120,1,0 -259,26,1,21.703,24,21.703,21.703,1,1,.,.,.,2,0,1,93.45371430621178,4.537,0,0,5,1,1,100,100,2,0,1,3,24,5,68.4,120,1,0 -260,26,1,50.514,48,50.514,50.514,3,2,.,.,.,2,0,1,18.035162119786612,2.892,0,0,5,1,1,100,100,2,0,1,3,24,5,68.4,120,1,0 -261,27,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,0,1,55,5,97,62,2,1 -262,27,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,0,1,55,5,97,62,2,0 -263,27,1,0.236,0.25,0.236,0.236,1,1,.,.,.,2,0,1,137.42754915883893,4.923,0,0,5,1,1,100,100,2,0,0,1,55,5,97,62,2,0 -264,27,1,0.537,0.5,0.537,0.537,1,1,.,.,.,2,0,1,238.43467211915168,5.474,0,0,5,1,1,100,100,2,0,0,1,55,5,97,62,2,0 -265,27,1,1.091,1,1.091,1.091,1,1,.,.,.,2,0,1,340.86594411945345,5.831,0,0,5,1,1,100,100,2,0,0,1,55,5,97,62,2,0 -266,27,1,4.193,4,4.193,4.193,1,1,.,.,.,2,0,1,238.0155245050968,5.472,0,0,5,1,1,100,100,2,0,0,1,55,5,97,62,2,0 -267,27,1,7.607,8,7.607,7.607,1,1,.,.,.,2,0,1,171.26553945487865,5.143,0,0,5,1,1,100,100,2,0,0,1,55,5,97,62,2,0 -268,27,1,10.895,12,10.895,10.895,1,1,.,.,.,2,0,1,171.7692389052334,5.146,0,0,5,1,1,100,100,2,0,0,1,55,5,97,62,2,0 -269,27,1,23.315,24,23.315,23.315,1,1,.,.,.,2,0,1,52.067285558051026,3.953,0,0,5,1,1,100,100,2,0,0,1,55,5,97,62,2,0 -270,27,1,48.017,48,48.017,48.017,3,2,.,.,.,2,0,1,16.375795531208155,2.796,0,0,5,1,1,100,100,2,0,0,1,55,5,97,62,2,0 -271,28,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,0,1,30,5,73.4,107,1,1 -272,28,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,0,1,30,5,73.4,107,1,0 -273,28,1,0.235,0.25,0.235,0.235,1,1,.,.,.,2,0,1,199.78359666315265,5.297,0,0,5,1,1,100,100,2,0,0,1,30,5,73.4,107,1,0 -274,28,1,0.47,0.5,0.47,0.47,1,1,.,.,.,2,0,1,328.899055432316,5.796,0,0,5,1,1,100,100,2,0,0,1,30,5,73.4,107,1,0 -275,28,1,0.988,1,0.988,0.988,1,1,.,.,.,2,0,1,360.6530379935688,5.888,0,0,5,1,1,100,100,2,0,0,1,30,5,73.4,107,1,0 -276,28,1,3.732,4,3.732,3.732,1,1,.,.,.,2,0,1,411.9183759810828,6.021,0,0,5,1,1,100,100,2,0,0,1,30,5,73.4,107,1,0 -277,28,1,7.525,8,7.525,7.525,1,1,.,.,.,2,0,1,240.8647333043881,5.484,0,0,5,1,1,100,100,2,0,0,1,30,5,73.4,107,1,0 -278,28,1,12.949,12,12.949,12.949,1,1,.,.,.,2,0,1,67.11009645588638,4.206,0,0,5,1,1,100,100,2,0,0,1,30,5,73.4,107,1,0 -279,28,1,22.676,24,22.676,22.676,1,1,.,.,.,2,0,1,46.38914180930702,3.837,0,0,5,1,1,100,100,2,0,0,1,30,5,73.4,107,1,0 -280,28,1,48.559,48,48.559,48.559,3,2,.,.,.,2,0,1,5.8506332712917875,1.767,0,0,5,1,1,100,100,2,0,0,1,30,5,73.4,107,1,0 -281,29,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,1,3,58,5,80.2,115,1,1 -282,29,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,1,3,58,5,80.2,115,1,0 -283,29,1,0.264,0.25,0.264,0.264,1,1,.,.,.,2,0,1,160.8814676510257,5.081,0,0,5,1,1,100,100,2,0,1,3,58,5,80.2,115,1,0 -284,29,1,0.473,0.5,0.473,0.473,1,1,.,.,.,2,0,1,404.1915881442617,6.002,0,0,5,1,1,100,100,2,0,1,3,58,5,80.2,115,1,0 -285,29,1,1.027,1,1.027,1.027,1,1,.,.,.,2,0,1,298.33358493377875,5.698,0,0,5,1,1,100,100,2,0,1,3,58,5,80.2,115,1,0 -286,29,1,4.12,4,4.12,4.12,1,1,.,.,.,2,0,1,292.7187971223755,5.679,0,0,5,1,1,100,100,2,0,1,3,58,5,80.2,115,1,0 -287,29,1,8.288,8,8.288,8.288,1,1,.,.,.,2,0,1,137.9153865093006,4.927,0,0,5,1,1,100,100,2,0,1,3,58,5,80.2,115,1,0 -288,29,1,13.072,12,13.072,13.072,1,1,.,.,.,2,0,1,119.17716220360658,4.781,0,0,5,1,1,100,100,2,0,1,3,58,5,80.2,115,1,0 -289,29,1,23.54,24,23.54,23.54,1,1,.,.,.,2,0,1,48.778083236087596,3.887,0,0,5,1,1,100,100,2,0,1,3,58,5,80.2,115,1,0 -290,29,1,43.985,48,43.985,43.985,2,2,.,.,.,2,0,1,11.699459354090898,2.46,0,0,5,1,1,100,100,2,0,1,3,58,5,80.2,115,1,0 -291,30,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,0,1,55,5,49.4,61,2,1 -292,30,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,0,1,55,5,49.4,61,2,0 -293,30,1,0.24,0.25,0.24,0.24,1,1,.,.,.,2,0,1,470.301049600942,6.153,0,0,5,1,1,100,100,2,0,0,1,55,5,49.4,61,2,0 -294,30,1,0.495,0.5,0.495,0.495,1,1,.,.,.,2,0,1,538.7876541360903,6.289,0,0,5,1,1,100,100,2,0,0,1,55,5,49.4,61,2,0 -295,30,1,1.009,1,1.009,1.009,1,1,.,.,.,2,0,1,545.4208017654174,6.302,0,0,5,1,1,100,100,2,0,0,1,55,5,49.4,61,2,0 -296,30,1,3.813,4,3.813,3.813,1,1,.,.,.,2,0,1,407.4062601637788,6.01,0,0,5,1,1,100,100,2,0,0,1,55,5,49.4,61,2,0 -297,30,1,8.245,8,8.245,8.245,1,1,.,.,.,2,0,1,267.5045139677714,5.589,0,0,5,1,1,100,100,2,0,0,1,55,5,49.4,61,2,0 -298,30,1,11.953,12,11.953,11.953,1,1,.,.,.,2,0,1,93.15730482418972,4.534,0,0,5,1,1,100,100,2,0,0,1,55,5,49.4,61,2,0 -299,30,1,25.113,24,25.113,25.113,2,1,.,.,.,2,0,1,64.63172628734895,4.169,0,0,5,1,1,100,100,2,0,0,1,55,5,49.4,61,2,0 -300,30,1,47.699,48,47.699,47.699,2,2,.,.,.,2,0,1,11.546995613104817,2.446,0,0,5,1,1,100,100,2,0,0,1,55,5,49.4,61,2,0 -301,31,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,0,1,25,5,76.7,66,2,1 -302,31,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,0,1,25,5,76.7,66,2,0 -303,31,1,0.227,0.25,0.227,0.227,1,1,.,.,.,2,0,1,156.40350769212318,5.052,0,0,5,1,1,100,100,2,0,0,1,25,5,76.7,66,2,0 -304,31,1,0.516,0.5,0.516,0.516,1,1,.,.,.,2,0,1,159.4846145658938,5.072,0,0,5,1,1,100,100,2,0,0,1,25,5,76.7,66,2,0 -305,31,1,0.976,1,0.976,0.976,1,1,.,.,.,2,0,1,288.5501768992313,5.665,0,0,5,1,1,100,100,2,0,0,1,25,5,76.7,66,2,0 -306,31,1,3.854,4,3.854,3.854,1,1,.,.,.,2,0,1,346.3679858899865,5.848,0,0,5,1,1,100,100,2,0,0,1,25,5,76.7,66,2,0 -307,31,1,8.066,8,8.066,8.066,1,1,.,.,.,2,0,1,278.95597475387945,5.631,0,0,5,1,1,100,100,2,0,0,1,25,5,76.7,66,2,0 -308,31,1,12.755,12,12.755,12.755,1,1,.,.,.,2,0,1,129.04158259324583,4.86,0,0,5,1,1,100,100,2,0,0,1,25,5,76.7,66,2,0 -309,31,1,21.603,24,21.603,21.603,1,1,.,.,.,2,0,1,72.26218254554098,4.28,0,0,5,1,1,100,100,2,0,0,1,25,5,76.7,66,2,0 -310,31,1,46.183,48,46.183,46.183,2,2,.,.,.,2,0,1,19.7177609385627,2.982,0,0,5,1,1,100,100,2,0,0,1,25,5,76.7,66,2,0 -311,32,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,1,3,59,5,68.7,87,2,1 -312,32,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,1,3,59,5,68.7,87,2,0 -313,32,1,0.239,0.25,0.239,0.239,1,1,.,.,.,2,0,1,209.38004714554103,5.344,0,0,5,1,1,100,100,2,0,1,3,59,5,68.7,87,2,0 -314,32,1,0.472,0.5,0.472,0.472,1,1,.,.,.,2,0,1,314.4394170518481,5.751,0,0,5,1,1,100,100,2,0,1,3,59,5,68.7,87,2,0 -315,32,1,1.021,1,1.021,1.021,1,1,.,.,.,2,0,1,534.0461042200109,6.28,0,0,5,1,1,100,100,2,0,1,3,59,5,68.7,87,2,0 -316,32,1,4.054,4,4.054,4.054,1,1,.,.,.,2,0,1,410.68238133210656,6.018,0,0,5,1,1,100,100,2,0,1,3,59,5,68.7,87,2,0 -317,32,1,8.364,8,8.364,8.364,1,1,.,.,.,2,0,1,356.4663033318391,5.876,0,0,5,1,1,100,100,2,0,1,3,59,5,68.7,87,2,0 -318,32,1,12.166,12,12.166,12.166,1,1,.,.,.,2,0,1,217.25311863708745,5.381,0,0,5,1,1,100,100,2,0,1,3,59,5,68.7,87,2,0 -319,32,1,22.393,24,22.393,22.393,1,1,.,.,.,2,0,1,90.91086830675995,4.51,0,0,5,1,1,100,100,2,0,1,3,59,5,68.7,87,2,0 -320,32,1,50.914,48,50.914,50.914,3,2,.,.,.,2,0,1,19.25921190118953,2.958,0,0,5,1,1,100,100,2,0,1,3,59,5,68.7,87,2,0 -321,33,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,0,1,55,5,118.4,93,1,1 -322,33,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,0,1,55,5,118.4,93,1,0 -323,33,1,0.243,0.25,0.243,0.243,1,1,.,.,.,2,0,1,213.21220595975265,5.362,0,0,5,1,1,100,100,2,0,0,1,55,5,118.4,93,1,0 -324,33,1,0.526,0.5,0.526,0.526,1,1,.,.,.,2,0,1,237.9325450852972,5.472,0,0,5,1,1,100,100,2,0,0,1,55,5,118.4,93,1,0 -325,33,1,0.945,1,0.945,0.945,1,1,.,.,.,2,0,1,412.148485301454,6.021,0,0,5,1,1,100,100,2,0,0,1,55,5,118.4,93,1,0 -326,33,1,3.744,4,3.744,3.744,1,1,.,.,.,2,0,1,258.31831462147653,5.554,0,0,5,1,1,100,100,2,0,0,1,55,5,118.4,93,1,0 -327,33,1,8.65,8,8.65,8.65,1,1,.,.,.,2,0,1,74.05403803351632,4.305,0,0,5,1,1,100,100,2,0,0,1,55,5,118.4,93,1,0 -328,33,1,12.504,12,12.504,12.504,1,1,.,.,.,2,0,1,69.30841715678122,4.239,0,0,5,1,1,100,100,2,0,0,1,55,5,118.4,93,1,0 -329,33,1,22.47,24,22.47,22.47,1,1,.,.,.,2,0,1,37.69253636479176,3.629,0,0,5,1,1,100,100,2,0,0,1,55,5,118.4,93,1,0 -330,33,1,51.637,48,51.637,51.637,3,2,.,.,.,2,0,1,.,.,1,1,5,1,1,100,100,2,0,0,1,55,5,118.4,93,1,0 -331,34,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,0,1,18,5,78.7,87,2,1 -332,34,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,0,1,18,5,78.7,87,2,0 -333,34,1,0.248,0.25,0.248,0.248,1,1,.,.,.,2,0,1,175.1841190719185,5.166,0,0,5,1,1,100,100,2,0,0,1,18,5,78.7,87,2,0 -334,34,1,0.484,0.5,0.484,0.484,1,1,.,.,.,2,0,1,255.66078932669444,5.544,0,0,5,1,1,100,100,2,0,0,1,18,5,78.7,87,2,0 -335,34,1,1.054,1,1.054,1.054,1,1,.,.,.,2,0,1,356.95756025824636,5.878,0,0,5,1,1,100,100,2,0,0,1,18,5,78.7,87,2,0 -336,34,1,4.35,4,4.35,4.35,1,1,.,.,.,2,0,1,421.6082101091455,6.044,0,0,5,1,1,100,100,2,0,0,1,18,5,78.7,87,2,0 -337,34,1,7.944,8,7.944,7.944,1,1,.,.,.,2,0,1,233.46589044472384,5.453,0,0,5,1,1,100,100,2,0,0,1,18,5,78.7,87,2,0 -338,34,1,12.021,12,12.021,12.021,1,1,.,.,.,2,0,1,159.56635697237425,5.072,0,0,5,1,1,100,100,2,0,0,1,18,5,78.7,87,2,0 -339,34,1,24.531,24,24.531,24.531,2,1,.,.,.,2,0,1,112.21137480018247,4.72,0,0,5,1,1,100,100,2,0,0,1,18,5,78.7,87,2,0 -340,34,1,50.619,48,50.619,50.619,3,2,.,.,.,2,0,1,15.135335879472295,2.717,0,0,5,1,1,100,100,2,0,0,1,18,5,78.7,87,2,0 -341,35,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,0,1,26,5,70.6,115,1,1 -342,35,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,0,1,26,5,70.6,115,1,0 -343,35,1,0.271,0.25,0.271,0.271,1,1,.,.,.,2,0,1,259.75032096874577,5.56,0,0,5,1,1,100,100,2,0,0,1,26,5,70.6,115,1,0 -344,35,1,0.541,0.5,0.541,0.541,1,1,.,.,.,2,0,1,355.6565453010379,5.874,0,0,5,1,1,100,100,2,0,0,1,26,5,70.6,115,1,0 -345,35,1,0.966,1,0.966,0.966,1,1,.,.,.,2,0,1,530.4126812723224,6.274,0,0,5,1,1,100,100,2,0,0,1,26,5,70.6,115,1,0 -346,35,1,4.382,4,4.382,4.382,1,1,.,.,.,2,0,1,247.91087855272198,5.513,0,0,5,1,1,100,100,2,0,0,1,26,5,70.6,115,1,0 -347,35,1,7.695,8,7.695,7.695,1,1,.,.,.,2,0,1,143.06893918328112,4.963,0,0,5,1,1,100,100,2,0,0,1,26,5,70.6,115,1,0 -348,35,1,11.916,12,11.916,11.916,1,1,.,.,.,2,0,1,69.63483550917275,4.243,0,0,5,1,1,100,100,2,0,0,1,26,5,70.6,115,1,0 -349,35,1,24.764,24,24.764,24.764,2,1,.,.,.,2,0,1,26.390315544961517,3.273,0,0,5,1,1,100,100,2,0,0,1,26,5,70.6,115,1,0 -350,35,1,43.365,48,43.365,43.365,2,2,.,.,.,2,0,1,6.121427045679128,1.812,0,0,5,1,1,100,100,2,0,0,1,26,5,70.6,115,1,0 -351,36,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,0,1,32,5,101.8,108,1,1 -352,36,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,0,1,32,5,101.8,108,1,0 -353,36,1,0.241,0.25,0.241,0.241,1,1,.,.,.,2,0,1,92.14330189090325,4.523,0,0,5,1,1,100,100,2,0,0,1,32,5,101.8,108,1,0 -354,36,1,0.489,0.5,0.489,0.489,1,1,.,.,.,2,0,1,130.06109300627864,4.868,0,0,5,1,1,100,100,2,0,0,1,32,5,101.8,108,1,0 -355,36,1,1.001,1,1.001,1.001,1,1,.,.,.,2,0,1,240.6604885297929,5.483,0,0,5,1,1,100,100,2,0,0,1,32,5,101.8,108,1,0 -356,36,1,3.922,4,3.922,3.922,1,1,.,.,.,2,0,1,194.5488680965122,5.271,0,0,5,1,1,100,100,2,0,0,1,32,5,101.8,108,1,0 -357,36,1,7.923,8,7.923,7.923,1,1,.,.,.,2,0,1,97.52918332984855,4.58,0,0,5,1,1,100,100,2,0,0,1,32,5,101.8,108,1,0 -358,36,1,11.307,12,11.307,11.307,1,1,.,.,.,2,0,1,79.59906491656149,4.377,0,0,5,1,1,100,100,2,0,0,1,32,5,101.8,108,1,0 -359,36,1,24.141,24,24.141,24.141,2,1,.,.,.,2,0,1,31.1037045050106,3.437,0,0,5,1,1,100,100,2,0,0,1,32,5,101.8,108,1,0 -360,36,1,45.224,48,45.224,45.224,2,2,.,.,.,2,0,1,7.104904209836037,1.961,0,0,5,1,1,100,100,2,0,0,1,32,5,101.8,108,1,0 -361,37,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,1,3,20,5,49.8,120,1,1 -362,37,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,1,3,20,5,49.8,120,1,0 -363,37,1,0.226,0.25,0.226,0.226,1,1,.,.,.,2,0,1,714.5875303451953,6.572,0,0,5,1,1,100,100,2,0,1,3,20,5,49.8,120,1,0 -364,37,1,0.457,0.5,0.457,0.457,1,1,.,.,.,2,0,1,505.27779167163965,6.225,0,0,5,1,1,100,100,2,0,1,3,20,5,49.8,120,1,0 -365,37,1,0.936,1,0.936,0.936,1,1,.,.,.,2,0,1,486.84902675893267,6.188,0,0,5,1,1,100,100,2,0,1,3,20,5,49.8,120,1,0 -366,37,1,3.843,4,3.843,3.843,1,1,.,.,.,2,0,1,499.8584610167993,6.214,0,0,5,1,1,100,100,2,0,1,3,20,5,49.8,120,1,0 -367,37,1,8.596,8,8.596,8.596,1,1,.,.,.,2,0,1,294.88663312329834,5.687,0,0,5,1,1,100,100,2,0,1,3,20,5,49.8,120,1,0 -368,37,1,12.729,12,12.729,12.729,1,1,.,.,.,2,0,1,171.45748076351995,5.144,0,0,5,1,1,100,100,2,0,1,3,20,5,49.8,120,1,0 -369,37,1,23.454,24,23.454,23.454,1,1,.,.,.,2,0,1,74.51515345731454,4.311,0,0,5,1,1,100,100,2,0,1,3,20,5,49.8,120,1,0 -370,37,1,47.366,48,47.366,47.366,2,2,.,.,.,2,0,1,11.36446760669203,2.43,0,0,5,1,1,100,100,2,0,1,3,20,5,49.8,120,1,0 -371,38,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,1,3,33,5,81.5,114,1,1 -372,38,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,1,3,33,5,81.5,114,1,0 -373,38,1,0.242,0.25,0.242,0.242,1,1,.,.,.,2,0,1,146.6037367551065,4.988,0,0,5,1,1,100,100,2,0,1,3,33,5,81.5,114,1,0 -374,38,1,0.465,0.5,0.465,0.465,1,1,.,.,.,2,0,1,141.58748922011867,4.953,0,0,5,1,1,100,100,2,0,1,3,33,5,81.5,114,1,0 -375,38,1,1.009,1,1.009,1.009,1,1,.,.,.,2,0,1,347.37060742071884,5.85,0,0,5,1,1,100,100,2,0,1,3,33,5,81.5,114,1,0 -376,38,1,4.036,4,4.036,4.036,1,1,.,.,.,2,0,1,314.0473880836127,5.75,0,0,5,1,1,100,100,2,0,1,3,33,5,81.5,114,1,0 -377,38,1,7.383,8,7.383,7.383,1,1,.,.,.,2,0,1,289.44340519216433,5.668,0,0,5,1,1,100,100,2,0,1,3,33,5,81.5,114,1,0 -378,38,1,11.599,12,11.599,11.599,1,1,.,.,.,2,0,1,202.39931424507364,5.31,0,0,5,1,1,100,100,2,0,1,3,33,5,81.5,114,1,0 -379,38,1,22.423,24,22.423,22.423,1,1,.,.,.,2,0,1,98.29273078592666,4.588,0,0,5,1,1,100,100,2,0,1,3,33,5,81.5,114,1,0 -380,38,1,50.282,48,50.282,50.282,3,2,.,.,.,2,0,1,25.87417426625283,3.253,0,0,5,1,1,100,100,2,0,1,3,33,5,81.5,114,1,0 -381,39,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,1,3,32,5,94.8,81,2,1 -382,39,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,1,3,32,5,94.8,81,2,0 -383,39,1,0.272,0.25,0.272,0.272,1,1,.,.,.,2,0,1,119.42862979309571,4.783,0,0,5,1,1,100,100,2,0,1,3,32,5,94.8,81,2,0 -384,39,1,0.51,0.5,0.51,0.51,1,1,.,.,.,2,0,1,119.39773889458473,4.782,0,0,5,1,1,100,100,2,0,1,3,32,5,94.8,81,2,0 -385,39,1,0.929,1,0.929,0.929,1,1,.,.,.,2,0,1,213.7534135643262,5.365,0,0,5,1,1,100,100,2,0,1,3,32,5,94.8,81,2,0 -386,39,1,4.313,4,4.313,4.313,1,1,.,.,.,2,0,1,224.59238200390578,5.414,0,0,5,1,1,100,100,2,0,1,3,32,5,94.8,81,2,0 -387,39,1,7.393,8,7.393,7.393,1,1,.,.,.,2,0,1,225.611014091282,5.419,0,0,5,1,1,100,100,2,0,1,3,32,5,94.8,81,2,0 -388,39,1,12.781,12,12.781,12.781,1,1,.,.,.,2,0,1,148.69205067377013,5.002,0,0,5,1,1,100,100,2,0,1,3,32,5,94.8,81,2,0 -389,39,1,23.54,24,23.54,23.54,1,1,.,.,.,2,0,1,86.76132562525922,4.463,0,0,5,1,1,100,100,2,0,1,3,32,5,94.8,81,2,0 -390,39,1,48.716,48,48.716,48.716,3,2,.,.,.,2,0,1,17.93576305519209,2.887,0,0,5,1,1,100,100,2,0,1,3,32,5,94.8,81,2,0 -391,40,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,100,100,2,0,0,1,36,5,88,90,1,1 -392,40,1,0,0,0,0,1,1,100,.,.,1,1,0,.,.,1,0,5,1,1,100,100,2,0,0,1,36,5,88,90,1,0 -393,40,1,0.238,0.25,0.238,0.238,1,1,.,.,.,2,0,1,74.18710364140956,4.307,0,0,5,1,1,100,100,2,0,0,1,36,5,88,90,1,0 -394,40,1,0.509,0.5,0.509,0.509,1,1,.,.,.,2,0,1,149.59183530698542,5.008,0,0,5,1,1,100,100,2,0,0,1,36,5,88,90,1,0 -395,40,1,0.987,1,0.987,0.987,1,1,.,.,.,2,0,1,258.033567072461,5.553,0,0,5,1,1,100,100,2,0,0,1,36,5,88,90,1,0 -396,40,1,4.321,4,4.321,4.321,1,1,.,.,.,2,0,1,302.7348011590723,5.713,0,0,5,1,1,100,100,2,0,0,1,36,5,88,90,1,0 -397,40,1,8.479,8,8.479,8.479,1,1,.,.,.,2,0,1,210.77800562478274,5.351,0,0,5,1,1,100,100,2,0,0,1,36,5,88,90,1,0 -398,40,1,11.36,12,11.36,11.36,1,1,.,.,.,2,0,1,125.81110899573154,4.835,0,0,5,1,1,100,100,2,0,0,1,36,5,88,90,1,0 -399,40,1,26.127,24,26.127,26.127,2,1,.,.,.,2,0,1,64.22148187958773,4.162,0,0,5,1,1,100,100,2,0,0,1,36,5,88,90,1,0 -400,40,1,48.143,48,48.143,48.143,3,2,.,.,.,2,0,1,18.97678035742815,2.943,0,0,5,1,1,100,100,2,0,0,1,36,5,88,90,1,0 -401,41,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,1,3,30,5,64.6,67,2,1 -402,41,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,1,3,30,5,64.6,67,2,0 -403,41,1,0.244,0.25,0.244,0.244,1,1,.,.,.,2,0,1,1210.196088378619,7.099,0,0,5,1,1,500,500,3,0,1,3,30,5,64.6,67,2,0 -404,41,1,0.518,0.5,0.518,0.518,1,1,.,.,.,2,0,1,1489.9712370941954,7.307,0,0,5,1,1,500,500,3,0,1,3,30,5,64.6,67,2,0 -405,41,1,1.044,1,1.044,1.044,1,1,.,.,.,2,0,1,1662.9062446182757,7.416,0,0,5,1,1,500,500,3,0,1,3,30,5,64.6,67,2,0 -406,41,1,3.709,4,3.709,3.709,1,1,.,.,.,2,0,1,3050.030677854217,8.023,0,0,5,1,1,500,500,3,0,1,3,30,5,64.6,67,2,0 -407,41,1,8.589,8,8.589,8.589,1,1,.,.,.,2,0,1,1634.2413748597758,7.399,0,0,5,1,1,500,500,3,0,1,3,30,5,64.6,67,2,0 -408,41,1,12.619,12,12.619,12.619,1,1,.,.,.,2,0,1,1218.8235200879453,7.106,0,0,5,1,1,500,500,3,0,1,3,30,5,64.6,67,2,0 -409,41,1,23.299,24,23.299,23.299,1,1,.,.,.,2,0,1,646.6535479153325,6.472,0,0,5,1,1,500,500,3,0,1,3,30,5,64.6,67,2,0 -410,41,1,45.557,48,45.557,45.557,2,2,.,.,.,2,0,1,183.31471481657607,5.211,0,0,5,1,1,500,500,3,0,1,3,30,5,64.6,67,2,0 -411,42,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,1,1,38,5,106.9,104,1,1 -412,42,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,1,1,38,5,106.9,104,1,0 -413,42,1,0.238,0.25,0.238,0.238,1,1,.,.,.,2,0,1,694.1577407394153,6.543,0,0,5,1,1,500,500,3,0,1,1,38,5,106.9,104,1,0 -414,42,1,0.498,0.5,0.498,0.498,1,1,.,.,.,2,0,1,1177.223612592839,7.071,0,0,5,1,1,500,500,3,0,1,1,38,5,106.9,104,1,0 -415,42,1,0.941,1,0.941,0.941,1,1,.,.,.,2,0,1,1675.185070328822,7.424,0,0,5,1,1,500,500,3,0,1,1,38,5,106.9,104,1,0 -416,42,1,4.064,4,4.064,4.064,1,1,.,.,.,2,0,1,1240.0920678851246,7.123,0,0,5,1,1,500,500,3,0,1,1,38,5,106.9,104,1,0 -417,42,1,8.707,8,8.707,8.707,1,1,.,.,.,2,0,1,389.8225287977387,5.966,0,0,5,1,1,500,500,3,0,1,1,38,5,106.9,104,1,0 -418,42,1,11.334,12,11.334,11.334,1,1,.,.,.,2,0,1,691.1182650238636,6.538,0,0,5,1,1,500,500,3,0,1,1,38,5,106.9,104,1,0 -419,42,1,25.728,24,25.728,25.728,2,1,.,.,.,2,0,1,165.58136357618537,5.109,0,0,5,1,1,500,500,3,0,1,1,38,5,106.9,104,1,0 -420,42,1,45.205,48,45.205,45.205,2,2,.,.,.,2,0,1,39.55613108072633,3.678,0,0,5,1,1,500,500,3,0,1,1,38,5,106.9,104,1,0 -421,43,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,0,1,60,5,51,95,1,1 -422,43,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,0,1,60,5,51,95,1,0 -423,43,1,0.257,0.25,0.257,0.257,1,1,.,.,.,2,0,1,1258.8544769411933,7.138,0,0,5,1,1,500,500,3,0,0,1,60,5,51,95,1,0 -424,43,1,0.504,0.5,0.504,0.504,1,1,.,.,.,2,0,1,2845.8104635202058,7.954,0,0,5,1,1,500,500,3,0,0,1,60,5,51,95,1,0 -425,43,1,1.098,1,1.098,1.098,1,1,.,.,.,2,0,1,1501.2865133670275,7.314,0,0,5,1,1,500,500,3,0,0,1,60,5,51,95,1,0 -426,43,1,4.073,4,4.073,4.073,1,1,.,.,.,2,0,1,2895.0981410221657,7.971,0,0,5,1,1,500,500,3,0,0,1,60,5,51,95,1,0 -427,43,1,8.381,8,8.381,8.381,1,1,.,.,.,2,0,1,1900.2506123458502,7.55,0,0,5,1,1,500,500,3,0,0,1,60,5,51,95,1,0 -428,43,1,11.461,12,11.461,11.461,1,1,.,.,.,2,0,1,1244.4713734811296,7.126,0,0,5,1,1,500,500,3,0,0,1,60,5,51,95,1,0 -429,43,1,25.87,24,25.87,25.87,2,1,.,.,.,2,0,1,452.49576201265836,6.115,0,0,5,1,1,500,500,3,0,0,1,60,5,51,95,1,0 -430,43,1,47.95,48,47.95,47.95,2,2,.,.,.,2,0,1,96.61471535118692,4.571,0,0,5,1,1,500,500,3,0,0,1,60,5,51,95,1,0 -431,44,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,1,3,40,5,85.2,116,1,1 -432,44,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,1,3,40,5,85.2,116,1,0 -433,44,1,0.267,0.25,0.267,0.267,1,1,.,.,.,2,0,1,694.322069543139,6.543,0,0,5,1,1,500,500,3,0,1,3,40,5,85.2,116,1,0 -434,44,1,0.514,0.5,0.514,0.514,1,1,.,.,.,2,0,1,1362.2730299251068,7.217,0,0,5,1,1,500,500,3,0,1,3,40,5,85.2,116,1,0 -435,44,1,1.007,1,1.007,1.007,1,1,.,.,.,2,0,1,1773.3641652706197,7.481,0,0,5,1,1,500,500,3,0,1,3,40,5,85.2,116,1,0 -436,44,1,4.22,4,4.22,4.22,1,1,.,.,.,2,0,1,1424.1795681153835,7.261,0,0,5,1,1,500,500,3,0,1,3,40,5,85.2,116,1,0 -437,44,1,8.58,8,8.58,8.58,1,1,.,.,.,2,0,1,545.3931726868615,6.302,0,0,5,1,1,500,500,3,0,1,3,40,5,85.2,116,1,0 -438,44,1,13.032,12,13.032,13.032,1,1,.,.,.,2,0,1,434.8986097896178,6.075,0,0,5,1,1,500,500,3,0,1,3,40,5,85.2,116,1,0 -439,44,1,23.386,24,23.386,23.386,1,1,.,.,.,2,0,1,253.72014105895803,5.536,0,0,5,1,1,500,500,3,0,1,3,40,5,85.2,116,1,0 -440,44,1,46.653,48,46.653,46.653,2,2,.,.,.,2,0,1,54.35172527397407,3.995,0,0,5,1,1,500,500,3,0,1,3,40,5,85.2,116,1,0 -441,45,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,1,3,56,5,86.4,73,2,1 -442,45,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,1,3,56,5,86.4,73,2,0 -443,45,1,0.246,0.25,0.246,0.246,1,1,.,.,.,2,0,1,804.8702810726105,6.691,0,0,5,1,1,500,500,3,0,1,3,56,5,86.4,73,2,0 -444,45,1,0.536,0.5,0.536,0.536,1,1,.,.,.,2,0,1,1181.3332224649944,7.074,0,0,5,1,1,500,500,3,0,1,3,56,5,86.4,73,2,0 -445,45,1,1.063,1,1.063,1.063,1,1,.,.,.,2,0,1,2497.028045612403,7.823,0,0,5,1,1,500,500,3,0,1,3,56,5,86.4,73,2,0 -446,45,1,3.964,4,3.964,3.964,1,1,.,.,.,2,0,1,2512.2240552920193,7.829,0,0,5,1,1,500,500,3,0,1,3,56,5,86.4,73,2,0 -447,45,1,7.543,8,7.543,7.543,1,1,.,.,.,2,0,1,1333.5868782689322,7.196,0,0,5,1,1,500,500,3,0,1,3,56,5,86.4,73,2,0 -448,45,1,11.411,12,11.411,11.411,1,1,.,.,.,2,0,1,820.4219622397109,6.71,0,0,5,1,1,500,500,3,0,1,3,56,5,86.4,73,2,0 -449,45,1,23.83,24,23.83,23.83,1,1,.,.,.,2,0,1,375.20564495957007,5.927,0,0,5,1,1,500,500,3,0,1,3,56,5,86.4,73,2,0 -450,45,1,46.655,48,46.655,46.655,2,2,.,.,.,2,0,1,122.25741617796973,4.806,0,0,5,1,1,500,500,3,0,1,3,56,5,86.4,73,2,0 -451,46,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,1,3,20,5,55,94,1,1 -452,46,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,1,3,20,5,55,94,1,0 -453,46,1,0.275,0.25,0.275,0.275,1,1,.,.,.,2,0,1,530.1141147953124,6.273,0,0,5,1,1,500,500,3,0,1,3,20,5,55,94,1,0 -454,46,1,0.461,0.5,0.461,0.461,1,1,.,.,.,2,0,1,1574.7873845976521,7.362,0,0,5,1,1,500,500,3,0,1,3,20,5,55,94,1,0 -455,46,1,0.901,1,0.901,0.901,1,1,.,.,.,2,0,1,1996.340517165985,7.599,0,0,5,1,1,500,500,3,0,1,3,20,5,55,94,1,0 -456,46,1,3.938,4,3.938,3.938,1,1,.,.,.,2,0,1,2906.739010784798,7.975,0,0,5,1,1,500,500,3,0,1,3,20,5,55,94,1,0 -457,46,1,8.367,8,8.367,8.367,1,1,.,.,.,2,0,1,2081.86044079574,7.641,0,0,5,1,1,500,500,3,0,1,3,20,5,55,94,1,0 -458,46,1,12.989,12,12.989,12.989,1,1,.,.,.,2,0,1,1362.6834120405356,7.217,0,0,5,1,1,500,500,3,0,1,3,20,5,55,94,1,0 -459,46,1,23.458,24,23.458,23.458,1,1,.,.,.,2,0,1,612.8696360409117,6.418,0,0,5,1,1,500,500,3,0,1,3,20,5,55,94,1,0 -460,46,1,43.546,48,43.546,43.546,2,2,.,.,.,2,0,1,293.32085071643394,5.681,0,0,5,1,1,500,500,3,0,1,3,20,5,55,94,1,0 -461,47,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,0,1,57,5,46.3,81,2,1 -462,47,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,0,1,57,5,46.3,81,2,0 -463,47,1,0.242,0.25,0.242,0.242,1,1,.,.,.,2,0,1,850.9881094935341,6.746,0,0,5,1,1,500,500,3,0,0,1,57,5,46.3,81,2,0 -464,47,1,0.505,0.5,0.505,0.505,1,1,.,.,.,2,0,1,1408.8070626275771,7.25,0,0,5,1,1,500,500,3,0,0,1,57,5,46.3,81,2,0 -465,47,1,1.026,1,1.026,1.026,1,1,.,.,.,2,0,1,2239.345814972832,7.714,0,0,5,1,1,500,500,3,0,0,1,57,5,46.3,81,2,0 -466,47,1,4.27,4,4.27,4.27,1,1,.,.,.,2,0,1,2251.9592373789524,7.72,0,0,5,1,1,500,500,3,0,0,1,57,5,46.3,81,2,0 -467,47,1,8.634,8,8.634,8.634,1,1,.,.,.,2,0,1,992.8547164511903,6.901,0,0,5,1,1,500,500,3,0,0,1,57,5,46.3,81,2,0 -468,47,1,11.214,12,11.214,11.214,1,1,.,.,.,2,0,1,1286.4675602822183,7.16,0,0,5,1,1,500,500,3,0,0,1,57,5,46.3,81,2,0 -469,47,1,22.826,24,22.826,22.826,1,1,.,.,.,2,0,1,324.8180696915133,5.783,0,0,5,1,1,500,500,3,0,0,1,57,5,46.3,81,2,0 -470,47,1,45.562,48,45.562,45.562,2,2,.,.,.,2,0,1,62.450503700111874,4.134,0,0,5,1,1,500,500,3,0,0,1,57,5,46.3,81,2,0 -471,48,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,1,1,56,5,51.5,107,1,1 -472,48,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,1,1,56,5,51.5,107,1,0 -473,48,1,0.226,0.25,0.226,0.226,1,1,.,.,.,2,0,1,594.1945716822511,6.387,0,0,5,1,1,500,500,3,0,1,1,56,5,51.5,107,1,0 -474,48,1,0.517,0.5,0.517,0.517,1,1,.,.,.,2,0,1,929.0369170922156,6.834,0,0,5,1,1,500,500,3,0,1,1,56,5,51.5,107,1,0 -475,48,1,0.973,1,0.973,0.973,1,1,.,.,.,2,0,1,2228.266905433397,7.709,0,0,5,1,1,500,500,3,0,1,1,56,5,51.5,107,1,0 -476,48,1,3.753,4,3.753,3.753,1,1,.,.,.,2,0,1,2206.4914728954755,7.699,0,0,5,1,1,500,500,3,0,1,1,56,5,51.5,107,1,0 -477,48,1,8.567,8,8.567,8.567,1,1,.,.,.,2,0,1,1190.6485468609856,7.082,0,0,5,1,1,500,500,3,0,1,1,56,5,51.5,107,1,0 -478,48,1,11.395,12,11.395,11.395,1,1,.,.,.,2,0,1,844.8786553764309,6.739,0,0,5,1,1,500,500,3,0,1,1,56,5,51.5,107,1,0 -479,48,1,21.948,24,21.948,21.948,1,1,.,.,.,2,0,1,141.0140402566954,4.949,0,0,5,1,1,500,500,3,0,1,1,56,5,51.5,107,1,0 -480,48,1,51.171,48,51.171,51.171,3,2,.,.,.,2,0,1,24.952635015648212,3.217,0,0,5,1,1,500,500,3,0,1,1,56,5,51.5,107,1,0 -481,49,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,1,1,60,5,63.2,96,1,1 -482,49,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,1,1,60,5,63.2,96,1,0 -483,49,1,0.236,0.25,0.236,0.236,1,1,.,.,.,2,0,1,1152.4782799621603,7.05,0,0,5,1,1,500,500,3,0,1,1,60,5,63.2,96,1,0 -484,49,1,0.531,0.5,0.531,0.531,1,1,.,.,.,2,0,1,1172.9168753436213,7.067,0,0,5,1,1,500,500,3,0,1,1,60,5,63.2,96,1,0 -485,49,1,1.071,1,1.071,1.071,1,1,.,.,.,2,0,1,2276.057567785654,7.73,0,0,5,1,1,500,500,3,0,1,1,60,5,63.2,96,1,0 -486,49,1,4.349,4,4.349,4.349,1,1,.,.,.,2,0,1,1518.3472723009234,7.325,0,0,5,1,1,500,500,3,0,1,1,60,5,63.2,96,1,0 -487,49,1,8.487,8,8.487,8.487,1,1,.,.,.,2,0,1,552.0924722781742,6.314,0,0,5,1,1,500,500,3,0,1,1,60,5,63.2,96,1,0 -488,49,1,10.909,12,10.909,10.909,1,1,.,.,.,2,0,1,204.69141835344024,5.322,0,0,5,1,1,500,500,3,0,1,1,60,5,63.2,96,1,0 -489,49,1,22.909,24,22.909,22.909,1,1,.,.,.,2,0,1,73.96258761618584,4.304,0,0,5,1,1,500,500,3,0,1,1,60,5,63.2,96,1,0 -490,49,1,48.601,48,48.601,48.601,3,2,.,.,.,2,0,1,9.298505884937125,2.23,0,0,5,1,1,500,500,3,0,1,1,60,5,63.2,96,1,0 -491,50,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,0,1,55,5,62,88,2,1 -492,50,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,0,1,55,5,62,88,2,0 -493,50,1,0.26,0.25,0.26,0.26,1,1,.,.,.,2,0,1,1268.7642782053022,7.146,0,0,5,1,1,500,500,3,0,0,1,55,5,62,88,2,0 -494,50,1,0.525,0.5,0.525,0.525,1,1,.,.,.,2,0,1,1870.8107483071844,7.534,0,0,5,1,1,500,500,3,0,0,1,55,5,62,88,2,0 -495,50,1,0.917,1,0.917,0.917,1,1,.,.,.,2,0,1,2806.429763197388,7.94,0,0,5,1,1,500,500,3,0,0,1,55,5,62,88,2,0 -496,50,1,4.158,4,4.158,4.158,1,1,.,.,.,2,0,1,2055.258599045424,7.628,0,0,5,1,1,500,500,3,0,0,1,55,5,62,88,2,0 -497,50,1,7.812,8,7.812,7.812,1,1,.,.,.,2,0,1,1161.0777684680008,7.057,0,0,5,1,1,500,500,3,0,0,1,55,5,62,88,2,0 -498,50,1,12.98,12,12.98,12.98,1,1,.,.,.,2,0,1,413.9559710466597,6.026,0,0,5,1,1,500,500,3,0,0,1,55,5,62,88,2,0 -499,50,1,22.278,24,22.278,22.278,1,1,.,.,.,2,0,1,281.1656255092639,5.639,0,0,5,1,1,500,500,3,0,0,1,55,5,62,88,2,0 -500,50,1,46.059,48,46.059,46.059,2,2,.,.,.,2,0,1,28.368968367091828,3.345,0,0,5,1,1,500,500,3,0,0,1,55,5,62,88,2,0 -501,51,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,1,3,60,5,54.5,86,2,1 -502,51,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,1,3,60,5,54.5,86,2,0 -503,51,1,0.262,0.25,0.262,0.262,1,1,.,.,.,2,0,1,377.34965495114795,5.933,0,0,5,1,1,500,500,3,0,1,3,60,5,54.5,86,2,0 -504,51,1,0.474,0.5,0.474,0.474,1,1,.,.,.,2,0,1,1723.6807022722144,7.452,0,0,5,1,1,500,500,3,0,1,3,60,5,54.5,86,2,0 -505,51,1,1.093,1,1.093,1.093,1,1,.,.,.,2,0,1,2985.04693083381,8.001,0,0,5,1,1,500,500,3,0,1,3,60,5,54.5,86,2,0 -506,51,1,4.382,4,4.382,4.382,1,1,.,.,.,2,0,1,2454.769906316115,7.806,0,0,5,1,1,500,500,3,0,1,3,60,5,54.5,86,2,0 -507,51,1,7.289,8,7.289,7.289,1,1,.,.,.,2,0,1,2225.937879401348,7.708,0,0,5,1,1,500,500,3,0,1,3,60,5,54.5,86,2,0 -508,51,1,11.422,12,11.422,11.422,1,1,.,.,.,2,0,1,1886.2020335135392,7.542,0,0,5,1,1,500,500,3,0,1,3,60,5,54.5,86,2,0 -509,51,1,26.15,24,26.15,26.15,2,1,.,.,.,2,0,1,724.8118891816262,6.586,0,0,5,1,1,500,500,3,0,1,3,60,5,54.5,86,2,0 -510,51,1,44.434,48,44.434,44.434,2,2,.,.,.,2,0,1,258.37952426646353,5.554,0,0,5,1,1,500,500,3,0,1,3,60,5,54.5,86,2,0 -511,52,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,0,1,55,5,86.7,110,1,1 -512,52,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,0,1,55,5,86.7,110,1,0 -513,52,1,0.25,0.25,0.25,0.25,1,1,.,.,.,2,0,1,850.8797750630605,6.746,0,0,5,1,1,500,500,3,0,0,1,55,5,86.7,110,1,0 -514,52,1,0.513,0.5,0.513,0.513,1,1,.,.,.,2,0,1,832.4959084492733,6.724,0,0,5,1,1,500,500,3,0,0,1,55,5,86.7,110,1,0 -515,52,1,0.905,1,0.905,0.905,1,1,.,.,.,2,0,1,1929.7487504575333,7.565,0,0,5,1,1,500,500,3,0,0,1,55,5,86.7,110,1,0 -516,52,1,3.713,4,3.713,3.713,1,1,.,.,.,2,0,1,1503.629837610226,7.316,0,0,5,1,1,500,500,3,0,0,1,55,5,86.7,110,1,0 -517,52,1,8.394,8,8.394,8.394,1,1,.,.,.,2,0,1,377.5273082658617,5.934,0,0,5,1,1,500,500,3,0,0,1,55,5,86.7,110,1,0 -518,52,1,11.624,12,11.624,11.624,1,1,.,.,.,2,0,1,366.1264126950567,5.903,0,0,5,1,1,500,500,3,0,0,1,55,5,86.7,110,1,0 -519,52,1,25.312,24,25.312,25.312,2,1,.,.,.,2,0,1,128.01771084857359,4.852,0,0,5,1,1,500,500,3,0,0,1,55,5,86.7,110,1,0 -520,52,1,47.499,48,47.499,47.499,2,2,.,.,.,2,0,1,24.934118115982074,3.216,0,0,5,1,1,500,500,3,0,0,1,55,5,86.7,110,1,0 -521,53,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,0,1,35,5,68.3,78,2,1 -522,53,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,0,1,35,5,68.3,78,2,0 -523,53,1,0.265,0.25,0.265,0.265,1,1,.,.,.,2,0,1,1060.8446797291638,6.967,0,0,5,1,1,500,500,3,0,0,1,35,5,68.3,78,2,0 -524,53,1,0.515,0.5,0.515,0.515,1,1,.,.,.,2,0,1,1155.9104854896611,7.053,0,0,5,1,1,500,500,3,0,0,1,35,5,68.3,78,2,0 -525,53,1,0.994,1,0.994,0.994,1,1,.,.,.,2,0,1,2467.4818936851025,7.811,0,0,5,1,1,500,500,3,0,0,1,35,5,68.3,78,2,0 -526,53,1,4.322,4,4.322,4.322,1,1,.,.,.,2,0,1,1895.5086966521187,7.547,0,0,5,1,1,500,500,3,0,0,1,35,5,68.3,78,2,0 -527,53,1,7.42,8,7.42,7.42,1,1,.,.,.,2,0,1,870.229857898761,6.769,0,0,5,1,1,500,500,3,0,0,1,35,5,68.3,78,2,0 -528,53,1,13.137,12,13.137,13.137,1,1,.,.,.,2,0,1,415.5624673134164,6.03,0,0,5,1,1,500,500,3,0,0,1,35,5,68.3,78,2,0 -529,53,1,23.334,24,23.334,23.334,1,1,.,.,.,2,0,1,166.28396823495498,5.114,0,0,5,1,1,500,500,3,0,0,1,35,5,68.3,78,2,0 -530,53,1,50.148,48,50.148,50.148,3,2,.,.,.,2,0,1,17.74027441920101,2.876,0,0,5,1,1,500,500,3,0,0,1,35,5,68.3,78,2,0 -531,54,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,0,1,58,5,54.4,88,2,1 -532,54,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,0,1,58,5,54.4,88,2,0 -533,54,1,0.228,0.25,0.228,0.228,1,1,.,.,.,2,0,1,1458.6233225069943,7.285,0,0,5,1,1,500,500,3,0,0,1,58,5,54.4,88,2,0 -534,54,1,0.535,0.5,0.535,0.535,1,1,.,.,.,2,0,1,1412.9101163175617,7.253,0,0,5,1,1,500,500,3,0,0,1,58,5,54.4,88,2,0 -535,54,1,1.069,1,1.069,1.069,1,1,.,.,.,2,0,1,2495.004713385167,7.822,0,0,5,1,1,500,500,3,0,0,1,58,5,54.4,88,2,0 -536,54,1,4.008,4,4.008,4.008,1,1,.,.,.,2,0,1,2418.646142051335,7.791,0,0,5,1,1,500,500,3,0,0,1,58,5,54.4,88,2,0 -537,54,1,7.513,8,7.513,7.513,1,1,.,.,.,2,0,1,1660.560678153732,7.415,0,0,5,1,1,500,500,3,0,0,1,58,5,54.4,88,2,0 -538,54,1,12.6,12,12.6,12.6,1,1,.,.,.,2,0,1,975.0038380739371,6.882,0,0,5,1,1,500,500,3,0,0,1,58,5,54.4,88,2,0 -539,54,1,22.528,24,22.528,22.528,1,1,.,.,.,2,0,1,371.0473205226491,5.916,0,0,5,1,1,500,500,3,0,0,1,58,5,54.4,88,2,0 -540,54,1,48.058,48,48.058,48.058,3,2,.,.,.,2,0,1,102.30140941716445,4.628,0,0,5,1,1,500,500,3,0,0,1,58,5,54.4,88,2,0 -541,55,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,0,1,40,5,54.3,115,1,1 -542,55,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,0,1,40,5,54.3,115,1,0 -543,55,1,0.274,0.25,0.274,0.274,1,1,.,.,.,2,0,1,1572.2894606730435,7.36,0,0,5,1,1,500,500,3,0,0,1,40,5,54.3,115,1,0 -544,55,1,0.529,0.5,0.529,0.529,1,1,.,.,.,2,0,1,2805.1353384757554,7.939,0,0,5,1,1,500,500,3,0,0,1,40,5,54.3,115,1,0 -545,55,1,1.066,1,1.066,1.066,1,1,.,.,.,2,0,1,3253.2051039704647,8.087,0,0,5,1,1,500,500,3,0,0,1,40,5,54.3,115,1,0 -546,55,1,4.304,4,4.304,4.304,1,1,.,.,.,2,0,1,1479.2145772588347,7.299,0,0,5,1,1,500,500,3,0,0,1,40,5,54.3,115,1,0 -547,55,1,7.541,8,7.541,7.541,1,1,.,.,.,2,0,1,585.9203449388576,6.373,0,0,5,1,1,500,500,3,0,0,1,40,5,54.3,115,1,0 -548,55,1,10.97,12,10.97,10.97,1,1,.,.,.,2,0,1,270.3560496775269,5.6,0,0,5,1,1,500,500,3,0,0,1,40,5,54.3,115,1,0 -549,55,1,25.005,24,25.005,25.005,2,1,.,.,.,2,0,1,47.07898307906825,3.852,0,0,5,1,1,500,500,3,0,0,1,40,5,54.3,115,1,0 -550,55,1,48.398,48,48.398,48.398,3,2,.,.,.,2,0,1,6.903995528243134,1.932,0,0,5,1,1,500,500,3,0,0,1,40,5,54.3,115,1,0 -551,56,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,1,3,21,5,99.7,77,2,1 -552,56,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,1,3,21,5,99.7,77,2,0 -553,56,1,0.243,0.25,0.243,0.243,1,1,.,.,.,2,0,1,946.709690029593,6.853,0,0,5,1,1,500,500,3,0,1,3,21,5,99.7,77,2,0 -554,56,1,0.519,0.5,0.519,0.519,1,1,.,.,.,2,0,1,1680.8649936506856,7.427,0,0,5,1,1,500,500,3,0,1,3,21,5,99.7,77,2,0 -555,56,1,0.966,1,0.966,0.966,1,1,.,.,.,2,0,1,1193.9148219311571,7.085,0,0,5,1,1,500,500,3,0,1,3,21,5,99.7,77,2,0 -556,56,1,3.684,4,3.684,3.684,1,1,.,.,.,2,0,1,2072.8490279950015,7.637,0,0,5,1,1,500,500,3,0,1,3,21,5,99.7,77,2,0 -557,56,1,8.634,8,8.634,8.634,1,1,.,.,.,2,0,1,917.6149613328456,6.822,0,0,5,1,1,500,500,3,0,1,3,21,5,99.7,77,2,0 -558,56,1,11.631,12,11.631,11.631,1,1,.,.,.,2,0,1,745.3643553874646,6.614,0,0,5,1,1,500,500,3,0,1,3,21,5,99.7,77,2,0 -559,56,1,24.472,24,24.472,24.472,2,1,.,.,.,2,0,1,335.7138940651394,5.816,0,0,5,1,1,500,500,3,0,1,3,21,5,99.7,77,2,0 -560,56,1,51.287,48,51.287,51.287,3,2,.,.,.,2,0,1,61.11965857174127,4.113,0,0,5,1,1,500,500,3,0,1,3,21,5,99.7,77,2,0 -561,57,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,1,3,36,5,80.8,77,2,1 -562,57,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,1,3,36,5,80.8,77,2,0 -563,57,1,0.267,0.25,0.267,0.267,1,1,.,.,.,2,0,1,1123.286470798349,7.024,0,0,5,1,1,500,500,3,0,1,3,36,5,80.8,77,2,0 -564,57,1,0.499,0.5,0.499,0.499,1,1,.,.,.,2,0,1,785.7262355711418,6.667,0,0,5,1,1,500,500,3,0,1,3,36,5,80.8,77,2,0 -565,57,1,0.972,1,0.972,0.972,1,1,.,.,.,2,0,1,2877.0707198232794,7.965,0,0,5,1,1,500,500,3,0,1,3,36,5,80.8,77,2,0 -566,57,1,3.688,4,3.688,3.688,1,1,.,.,.,2,0,1,1574.8750965012844,7.362,0,0,5,1,1,500,500,3,0,1,3,36,5,80.8,77,2,0 -567,57,1,8.054,8,8.054,8.054,1,1,.,.,.,2,0,1,1748.621423777175,7.467,0,0,5,1,1,500,500,3,0,1,3,36,5,80.8,77,2,0 -568,57,1,11,12,11,11,1,1,.,.,.,2,0,1,1095.916342499254,6.999,0,0,5,1,1,500,500,3,0,1,3,36,5,80.8,77,2,0 -569,57,1,24.933,24,24.933,24.933,2,1,.,.,.,2,0,1,394.97841383953994,5.979,0,0,5,1,1,500,500,3,0,1,3,36,5,80.8,77,2,0 -570,57,1,49.689,48,49.689,49.689,3,2,.,.,.,2,0,1,93.19307868160631,4.535,0,0,5,1,1,500,500,3,0,1,3,36,5,80.8,77,2,0 -571,58,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,1,3,60,5,63.8,84,2,1 -572,58,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,1,3,60,5,63.8,84,2,0 -573,58,1,0.255,0.25,0.255,0.255,1,1,.,.,.,2,0,1,1913.7772315664256,7.557,0,0,5,1,1,500,500,3,0,1,3,60,5,63.8,84,2,0 -574,58,1,0.547,0.5,0.547,0.547,1,1,.,.,.,2,0,1,2581.0915853161264,7.856,0,0,5,1,1,500,500,3,0,1,3,60,5,63.8,84,2,0 -575,58,1,1.035,1,1.035,1.035,1,1,.,.,.,2,0,1,3597.9389064354878,8.188,0,0,5,1,1,500,500,3,0,1,3,60,5,63.8,84,2,0 -576,58,1,4.085,4,4.085,4.085,1,1,.,.,.,2,0,1,1811.2144676641924,7.502,0,0,5,1,1,500,500,3,0,1,3,60,5,63.8,84,2,0 -577,58,1,7.585,8,7.585,7.585,1,1,.,.,.,2,0,1,1472.6453473176814,7.295,0,0,5,1,1,500,500,3,0,1,3,60,5,63.8,84,2,0 -578,58,1,12.481,12,12.481,12.481,1,1,.,.,.,2,0,1,698.9733789061412,6.55,0,0,5,1,1,500,500,3,0,1,3,60,5,63.8,84,2,0 -579,58,1,22.316,24,22.316,22.316,1,1,.,.,.,2,0,1,276.5146575515583,5.622,0,0,5,1,1,500,500,3,0,1,3,60,5,63.8,84,2,0 -580,58,1,46.448,48,46.448,46.448,2,2,.,.,.,2,0,1,57.33944072192072,4.049,0,0,5,1,1,500,500,3,0,1,3,60,5,63.8,84,2,0 -581,59,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,0,1,60,5,66.9,109,1,1 -582,59,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,0,1,60,5,66.9,109,1,0 -583,59,1,0.241,0.25,0.241,0.241,1,1,.,.,.,2,0,1,628.5393534237314,6.443,0,0,5,1,1,500,500,3,0,0,1,60,5,66.9,109,1,0 -584,59,1,0.485,0.5,0.485,0.485,1,1,.,.,.,2,0,1,1065.7579510391938,6.971,0,0,5,1,1,500,500,3,0,0,1,60,5,66.9,109,1,0 -585,59,1,0.957,1,0.957,0.957,1,1,.,.,.,2,0,1,1395.864194306801,7.241,0,0,5,1,1,500,500,3,0,0,1,60,5,66.9,109,1,0 -586,59,1,4.18,4,4.18,4.18,1,1,.,.,.,2,0,1,1344.3388794663667,7.204,0,0,5,1,1,500,500,3,0,0,1,60,5,66.9,109,1,0 -587,59,1,7.572,8,7.572,7.572,1,1,.,.,.,2,0,1,1209.5421854640351,7.098,0,0,5,1,1,500,500,3,0,0,1,60,5,66.9,109,1,0 -588,59,1,11.976,12,11.976,11.976,1,1,.,.,.,2,0,1,603.2815331180017,6.402,0,0,5,1,1,500,500,3,0,0,1,60,5,66.9,109,1,0 -589,59,1,26.036,24,26.036,26.036,2,1,.,.,.,2,0,1,286.1910559286342,5.657,0,0,5,1,1,500,500,3,0,0,1,60,5,66.9,109,1,0 -590,59,1,51.416,48,51.416,51.416,3,2,.,.,.,2,0,1,31.05321158951122,3.436,0,0,5,1,1,500,500,3,0,0,1,60,5,66.9,109,1,0 -591,60,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,500,500,3,0,1,3,39,5,64,71,2,1 -592,60,1,0,0,0,0,1,1,500,.,.,1,1,0,.,.,1,0,5,1,1,500,500,3,0,1,3,39,5,64,71,2,0 -593,60,1,0.269,0.25,0.269,0.269,1,1,.,.,.,2,0,1,1449.3840973088377,7.279,0,0,5,1,1,500,500,3,0,1,3,39,5,64,71,2,0 -594,60,1,0.462,0.5,0.462,0.462,1,1,.,.,.,2,0,1,2191.9543697964996,7.693,0,0,5,1,1,500,500,3,0,1,3,39,5,64,71,2,0 -595,60,1,1.045,1,1.045,1.045,1,1,.,.,.,2,0,1,3895.900739893765,8.268,0,0,5,1,1,500,500,3,0,1,3,39,5,64,71,2,0 -596,60,1,4.287,4,4.287,4.287,1,1,.,.,.,2,0,1,2282.900462994984,7.733,0,0,5,1,1,500,500,3,0,1,3,39,5,64,71,2,0 -597,60,1,7.648,8,7.648,7.648,1,1,.,.,.,2,0,1,1282.6361590571498,7.157,0,0,5,1,1,500,500,3,0,1,3,39,5,64,71,2,0 -598,60,1,11.703,12,11.703,11.703,1,1,.,.,.,2,0,1,1305.6544617912446,7.174,0,0,5,1,1,500,500,3,0,1,3,39,5,64,71,2,0 -599,60,1,25.218,24,25.218,25.218,2,1,.,.,.,2,0,1,463.6491348594561,6.139,0,0,5,1,1,500,500,3,0,1,3,39,5,64,71,2,0 -600,60,1,49.451,48,49.451,49.451,3,2,.,.,.,2,0,1,115.69291130989453,4.751,0,0,5,1,1,500,500,3,0,1,3,39,5,64,71,2,0 -601,61,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,19,5,79.1,73,2,1 -602,61,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,19,5,79.1,73,2,0 -603,61,1,0.241,0.25,0.241,0.241,1,1,.,.,.,2,0,1,907.8837171840179,6.811,0,0,5,1,1,1000,1000,4,0,0,1,19,5,79.1,73,2,0 -604,61,1,0.531,0.5,0.531,0.531,1,1,.,.,.,2,0,1,1578.8430094627006,7.364,0,0,5,1,1,1000,1000,4,0,0,1,19,5,79.1,73,2,0 -605,61,1,0.991,1,0.991,0.991,1,1,.,.,.,2,0,1,2579.6484623974516,7.855,0,0,5,1,1,1000,1000,4,0,0,1,19,5,79.1,73,2,0 -606,61,1,3.756,4,3.756,3.756,1,1,.,.,.,2,0,1,2090.538713488568,7.645,0,0,5,1,1,1000,1000,4,0,0,1,19,5,79.1,73,2,0 -607,61,1,7.287,8,7.287,7.287,1,1,.,.,.,2,0,1,1603.534655687105,7.38,0,0,5,1,1,1000,1000,4,0,0,1,19,5,79.1,73,2,0 -608,61,1,10.875,12,10.875,10.875,1,1,.,.,.,2,0,1,1499.1391836791177,7.313,0,0,5,1,1,1000,1000,4,0,0,1,19,5,79.1,73,2,0 -609,61,1,21.894,24,21.894,21.894,1,1,.,.,.,2,0,1,491.5518799764156,6.198,0,0,5,1,1,1000,1000,4,0,0,1,19,5,79.1,73,2,0 -610,61,1,51.576,48,51.576,51.576,3,2,.,.,.,2,0,1,20.971910676666333,3.043,0,0,5,1,1,1000,1000,4,0,0,1,19,5,79.1,73,2,0 -611,62,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,57,5,70.4,117,1,1 -612,62,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,57,5,70.4,117,1,0 -613,62,1,0.268,0.25,0.268,0.268,1,1,.,.,.,2,0,1,2421.262301924223,7.792,0,0,5,1,1,1000,1000,4,0,0,1,57,5,70.4,117,1,0 -614,62,1,0.478,0.5,0.478,0.478,1,1,.,.,.,2,0,1,3593.3678175797995,8.187,0,0,5,1,1,1000,1000,4,0,0,1,57,5,70.4,117,1,0 -615,62,1,1.017,1,1.017,1.017,1,1,.,.,.,2,0,1,4149.480142179834,8.331,0,0,5,1,1,1000,1000,4,0,0,1,57,5,70.4,117,1,0 -616,62,1,3.647,4,3.647,3.647,1,1,.,.,.,2,0,1,3125.4651060334827,8.047,0,0,5,1,1,1000,1000,4,0,0,1,57,5,70.4,117,1,0 -617,62,1,8.582,8,8.582,8.582,1,1,.,.,.,2,0,1,1454.8308059059584,7.283,0,0,5,1,1,1000,1000,4,0,0,1,57,5,70.4,117,1,0 -618,62,1,11.031,12,11.031,11.031,1,1,.,.,.,2,0,1,836.9548141010805,6.73,0,0,5,1,1,1000,1000,4,0,0,1,57,5,70.4,117,1,0 -619,62,1,23.909,24,23.909,23.909,1,1,.,.,.,2,0,1,311.87476463900873,5.743,0,0,5,1,1,1000,1000,4,0,0,1,57,5,70.4,117,1,0 -620,62,1,48.422,48,48.422,48.422,3,2,.,.,.,2,0,1,37.034885555325914,3.612,0,0,5,1,1,1000,1000,4,0,0,1,57,5,70.4,117,1,0 -621,63,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,55,5,52.1,91,1,1 -622,63,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,55,5,52.1,91,1,0 -623,63,1,0.259,0.25,0.259,0.259,1,1,.,.,.,2,0,1,1211.7149158738716,7.1,0,0,5,1,1,1000,1000,4,0,0,1,55,5,52.1,91,1,0 -624,63,1,0.509,0.5,0.509,0.509,1,1,.,.,.,2,0,1,1616.8712588103913,7.388,0,0,5,1,1,1000,1000,4,0,0,1,55,5,52.1,91,1,0 -625,63,1,1.044,1,1.044,1.044,1,1,.,.,.,2,0,1,3112.9369875151956,8.043,0,0,5,1,1,1000,1000,4,0,0,1,55,5,52.1,91,1,0 -626,63,1,3.706,4,3.706,3.706,1,1,.,.,.,2,0,1,2908.1795833212477,7.975,0,0,5,1,1,1000,1000,4,0,0,1,55,5,52.1,91,1,0 -627,63,1,7.494,8,7.494,7.494,1,1,.,.,.,2,0,1,1632.8315140931782,7.398,0,0,5,1,1,1000,1000,4,0,0,1,55,5,52.1,91,1,0 -628,63,1,13.076,12,13.076,13.076,1,1,.,.,.,2,0,1,944.5631098895316,6.851,0,0,5,1,1,1000,1000,4,0,0,1,55,5,52.1,91,1,0 -629,63,1,24.178,24,24.178,24.178,2,1,.,.,.,2,0,1,351.7556955123468,5.863,0,0,5,1,1,1000,1000,4,0,0,1,55,5,52.1,91,1,0 -630,63,1,50.134,48,50.134,50.134,3,2,.,.,.,2,0,1,37.10579267840746,3.614,0,0,5,1,1,1000,1000,4,0,0,1,55,5,52.1,91,1,0 -631,64,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,52,5,41.3,106,1,1 -632,64,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,52,5,41.3,106,1,0 -633,64,1,0.232,0.25,0.232,0.232,1,1,.,.,.,2,0,1,5247.688051177523,8.566,0,0,5,1,1,1000,1000,4,0,0,1,52,5,41.3,106,1,0 -634,64,1,0.514,0.5,0.514,0.514,1,1,.,.,.,2,0,1,3504.63598851183,8.162,0,0,5,1,1,1000,1000,4,0,0,1,52,5,41.3,106,1,0 -635,64,1,0.936,1,0.936,0.936,1,1,.,.,.,2,0,1,5727.597773408317,8.653,0,0,5,1,1,1000,1000,4,0,0,1,52,5,41.3,106,1,0 -636,64,1,3.716,4,3.716,3.716,1,1,.,.,.,2,0,1,5210.379048827275,8.558,0,0,5,1,1,1000,1000,4,0,0,1,52,5,41.3,106,1,0 -637,64,1,7.796,8,7.796,7.796,1,1,.,.,.,2,0,1,2118.954702359241,7.659,0,0,5,1,1,1000,1000,4,0,0,1,52,5,41.3,106,1,0 -638,64,1,12.369,12,12.369,12.369,1,1,.,.,.,2,0,1,1865.598116297122,7.531,0,0,5,1,1,1000,1000,4,0,0,1,52,5,41.3,106,1,0 -639,64,1,23.706,24,23.706,23.706,1,1,.,.,.,2,0,1,642.997998139496,6.466,0,0,5,1,1,1000,1000,4,0,0,1,52,5,41.3,106,1,0 -640,64,1,48.713,48,48.713,48.713,3,2,.,.,.,2,0,1,69.7448364258404,4.245,0,0,5,1,1,1000,1000,4,0,0,1,52,5,41.3,106,1,0 -641,65,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,33,5,53.1,101,1,1 -642,65,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,33,5,53.1,101,1,0 -643,65,1,0.265,0.25,0.265,0.265,1,1,.,.,.,2,0,1,2337.3846284444994,7.757,0,0,5,1,1,1000,1000,4,0,0,1,33,5,53.1,101,1,0 -644,65,1,0.547,0.5,0.547,0.547,1,1,.,.,.,2,0,1,2398.3655419519923,7.783,0,0,5,1,1,1000,1000,4,0,0,1,33,5,53.1,101,1,0 -645,65,1,1.079,1,1.079,1.079,1,1,.,.,.,2,0,1,4871.044986308018,8.491,0,0,5,1,1,1000,1000,4,0,0,1,33,5,53.1,101,1,0 -646,65,1,4.271,4,4.271,4.271,1,1,.,.,.,2,0,1,2103.555539850791,7.651,0,0,5,1,1,1000,1000,4,0,0,1,33,5,53.1,101,1,0 -647,65,1,7.5,8,7.5,7.5,1,1,.,.,.,2,0,1,2401.2362853248655,7.784,0,0,5,1,1,1000,1000,4,0,0,1,33,5,53.1,101,1,0 -648,65,1,11.2,12,11.2,11.2,1,1,.,.,.,2,0,1,1694.851829358077,7.435,0,0,5,1,1,1000,1000,4,0,0,1,33,5,53.1,101,1,0 -649,65,1,25.688,24,25.688,25.688,2,1,.,.,.,2,0,1,497.9472706982106,6.21,0,0,5,1,1,1000,1000,4,0,0,1,33,5,53.1,101,1,0 -650,65,1,44.17,48,44.17,44.17,2,2,.,.,.,2,0,1,103.16130892778757,4.636,0,0,5,1,1,1000,1000,4,0,0,1,33,5,53.1,101,1,0 -651,66,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,1,3,60,5,92.7,72,2,1 -652,66,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,1,3,60,5,92.7,72,2,0 -653,66,1,0.238,0.25,0.238,0.238,1,1,.,.,.,2,0,1,814.5759987602264,6.703,0,0,5,1,1,1000,1000,4,0,1,3,60,5,92.7,72,2,0 -654,66,1,0.527,0.5,0.527,0.527,1,1,.,.,.,2,0,1,2847.497681630019,7.954,0,0,5,1,1,1000,1000,4,0,1,3,60,5,92.7,72,2,0 -655,66,1,0.944,1,0.944,0.944,1,1,.,.,.,2,0,1,4556.419702619436,8.424,0,0,5,1,1,1000,1000,4,0,1,3,60,5,92.7,72,2,0 -656,66,1,3.761,4,3.761,3.761,1,1,.,.,.,2,0,1,3757.518938011308,8.232,0,0,5,1,1,1000,1000,4,0,1,3,60,5,92.7,72,2,0 -657,66,1,8.583,8,8.583,8.583,1,1,.,.,.,2,0,1,1668.5042356988292,7.42,0,0,5,1,1,1000,1000,4,0,1,3,60,5,92.7,72,2,0 -658,66,1,12.468,12,12.468,12.468,1,1,.,.,.,2,0,1,854.3149435232458,6.75,0,0,5,1,1,1000,1000,4,0,1,3,60,5,92.7,72,2,0 -659,66,1,21.632,24,21.632,21.632,1,1,.,.,.,2,0,1,1127.5927212165743,7.028,0,0,5,1,1,1000,1000,4,0,1,3,60,5,92.7,72,2,0 -660,66,1,45.772,48,45.772,45.772,2,2,.,.,.,2,0,1,396.7876391229974,5.983,0,0,5,1,1,1000,1000,4,0,1,3,60,5,92.7,72,2,0 -661,67,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,1,3,26,5,77.2,81,2,1 -662,67,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,1,3,26,5,77.2,81,2,0 -663,67,1,0.265,0.25,0.265,0.265,1,1,.,.,.,2,0,1,2298.4831358584242,7.74,0,0,5,1,1,1000,1000,4,0,1,3,26,5,77.2,81,2,0 -664,67,1,0.495,0.5,0.495,0.495,1,1,.,.,.,2,0,1,4286.497529449689,8.363,0,0,5,1,1,1000,1000,4,0,1,3,26,5,77.2,81,2,0 -665,67,1,1.005,1,1.005,1.005,1,1,.,.,.,2,0,1,3241.257755705435,8.084,0,0,5,1,1,1000,1000,4,0,1,3,26,5,77.2,81,2,0 -666,67,1,4.282,4,4.282,4.282,1,1,.,.,.,2,0,1,3293.6856648318853,8.1,0,0,5,1,1,1000,1000,4,0,1,3,26,5,77.2,81,2,0 -667,67,1,8.6,8,8.6,8.6,1,1,.,.,.,2,0,1,3515.464782769831,8.165,0,0,5,1,1,1000,1000,4,0,1,3,26,5,77.2,81,2,0 -668,67,1,12.54,12,12.54,12.54,1,1,.,.,.,2,0,1,1462.522836833489,7.288,0,0,5,1,1,1000,1000,4,0,1,3,26,5,77.2,81,2,0 -669,67,1,22.57,24,22.57,22.57,1,1,.,.,.,2,0,1,1048.6397524003173,6.955,0,0,5,1,1,1000,1000,4,0,1,3,26,5,77.2,81,2,0 -670,67,1,43.799,48,43.799,43.799,2,2,.,.,.,2,0,1,343.43617543513864,5.839,0,0,5,1,1,1000,1000,4,0,1,3,26,5,77.2,81,2,0 -671,68,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,29,5,72.1,72,2,1 -672,68,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,29,5,72.1,72,2,0 -673,68,1,0.227,0.25,0.227,0.227,1,1,.,.,.,2,0,1,1554.2826694561138,7.349,0,0,5,1,1,1000,1000,4,0,0,1,29,5,72.1,72,2,0 -674,68,1,0.469,0.5,0.469,0.469,1,1,.,.,.,2,0,1,2928.169375815853,7.982,0,0,5,1,1,1000,1000,4,0,0,1,29,5,72.1,72,2,0 -675,68,1,0.953,1,0.953,0.953,1,1,.,.,.,2,0,1,4204.931770553052,8.344,0,0,5,1,1,1000,1000,4,0,0,1,29,5,72.1,72,2,0 -676,68,1,3.931,4,3.931,3.931,1,1,.,.,.,2,0,1,3716.039796660728,8.22,0,0,5,1,1,1000,1000,4,0,0,1,29,5,72.1,72,2,0 -677,68,1,8.587,8,8.587,8.587,1,1,.,.,.,2,0,1,1582.1203981844467,7.367,0,0,5,1,1,1000,1000,4,0,0,1,29,5,72.1,72,2,0 -678,68,1,12.436,12,12.436,12.436,1,1,.,.,.,2,0,1,1129.9382261500955,7.03,0,0,5,1,1,1000,1000,4,0,0,1,29,5,72.1,72,2,0 -679,68,1,24.881,24,24.881,24.881,2,1,.,.,.,2,0,1,493.3389241209799,6.201,0,0,5,1,1,1000,1000,4,0,0,1,29,5,72.1,72,2,0 -680,68,1,46.005,48,46.005,46.005,2,2,.,.,.,2,0,1,79.25056969077485,4.373,0,0,5,1,1,1000,1000,4,0,0,1,29,5,72.1,72,2,0 -681,69,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,28,5,65.9,112,1,1 -682,69,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,28,5,65.9,112,1,0 -683,69,1,0.236,0.25,0.236,0.236,1,1,.,.,.,2,0,1,1470.281006300334,7.293,0,0,5,1,1,1000,1000,4,0,0,1,28,5,65.9,112,1,0 -684,69,1,0.458,0.5,0.458,0.458,1,1,.,.,.,2,0,1,2577.4638468687162,7.855,0,0,5,1,1,1000,1000,4,0,0,1,28,5,65.9,112,1,0 -685,69,1,0.962,1,0.962,0.962,1,1,.,.,.,2,0,1,2650.9593590301556,7.883,0,0,5,1,1,1000,1000,4,0,0,1,28,5,65.9,112,1,0 -686,69,1,4.285,4,4.285,4.285,1,1,.,.,.,2,0,1,4480.264586424101,8.407,0,0,5,1,1,1000,1000,4,0,0,1,28,5,65.9,112,1,0 -687,69,1,8.194,8,8.194,8.194,1,1,.,.,.,2,0,1,2277.435810039123,7.731,0,0,5,1,1,1000,1000,4,0,0,1,28,5,65.9,112,1,0 -688,69,1,11.322,12,11.322,11.322,1,1,.,.,.,2,0,1,1588.9965931795466,7.371,0,0,5,1,1,1000,1000,4,0,0,1,28,5,65.9,112,1,0 -689,69,1,22.575,24,22.575,22.575,1,1,.,.,.,2,0,1,586.2914132923653,6.374,0,0,5,1,1,1000,1000,4,0,0,1,28,5,65.9,112,1,0 -690,69,1,47.824,48,47.824,47.824,2,2,.,.,.,2,0,1,98.72608801426513,4.592,0,0,5,1,1,1000,1000,4,0,0,1,28,5,65.9,112,1,0 -691,70,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,30,5,91.1,69,2,1 -692,70,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,30,5,91.1,69,2,0 -693,70,1,0.244,0.25,0.244,0.244,1,1,.,.,.,2,0,1,776.1864030888195,6.654,0,0,5,1,1,1000,1000,4,0,0,1,30,5,91.1,69,2,0 -694,70,1,0.472,0.5,0.472,0.472,1,1,.,.,.,2,0,1,3019.9746028545896,8.013,0,0,5,1,1,1000,1000,4,0,0,1,30,5,91.1,69,2,0 -695,70,1,0.98,1,0.98,0.98,1,1,.,.,.,2,0,1,2380.93465353306,7.775,0,0,5,1,1,1000,1000,4,0,0,1,30,5,91.1,69,2,0 -696,70,1,3.782,4,3.782,3.782,1,1,.,.,.,2,0,1,2610.962931912519,7.867,0,0,5,1,1,1000,1000,4,0,0,1,30,5,91.1,69,2,0 -697,70,1,7.583,8,7.583,7.583,1,1,.,.,.,2,0,1,1101.3011411304615,7.004,0,0,5,1,1,1000,1000,4,0,0,1,30,5,91.1,69,2,0 -698,70,1,13.036,12,13.036,13.036,1,1,.,.,.,2,0,1,461.8141940007712,6.135,0,0,5,1,1,1000,1000,4,0,0,1,30,5,91.1,69,2,0 -699,70,1,24.671,24,24.671,24.671,2,1,.,.,.,2,0,1,279.99077417584317,5.635,0,0,5,1,1,1000,1000,4,0,0,1,30,5,91.1,69,2,0 -700,70,1,45.281,48,45.281,45.281,2,2,.,.,.,2,0,1,88.56866305922377,4.484,0,0,5,1,1,1000,1000,4,0,0,1,30,5,91.1,69,2,0 -701,71,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,39,5,78.7,94,1,1 -702,71,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,39,5,78.7,94,1,0 -703,71,1,0.238,0.25,0.238,0.238,1,1,.,.,.,2,0,1,1847.9929690503093,7.522,0,0,5,1,1,1000,1000,4,0,0,1,39,5,78.7,94,1,0 -704,71,1,0.475,0.5,0.475,0.475,1,1,.,.,.,2,0,1,2831.933884200879,7.949,0,0,5,1,1,1000,1000,4,0,0,1,39,5,78.7,94,1,0 -705,71,1,1.024,1,1.024,1.024,1,1,.,.,.,2,0,1,4497.25570558864,8.411,0,0,5,1,1,1000,1000,4,0,0,1,39,5,78.7,94,1,0 -706,71,1,3.989,4,3.989,3.989,1,1,.,.,.,2,0,1,3236.9157243988097,8.082,0,0,5,1,1,1000,1000,4,0,0,1,39,5,78.7,94,1,0 -707,71,1,8.767,8,8.767,8.767,1,1,.,.,.,2,0,1,2128.5766624774474,7.663,0,0,5,1,1,1000,1000,4,0,0,1,39,5,78.7,94,1,0 -708,71,1,13.002,12,13.002,13.002,1,1,.,.,.,2,0,1,1084.7092310780572,6.989,0,0,5,1,1,1000,1000,4,0,0,1,39,5,78.7,94,1,0 -709,71,1,25.75,24,25.75,25.75,2,1,.,.,.,2,0,1,791.3971841667451,6.674,0,0,5,1,1,1000,1000,4,0,0,1,39,5,78.7,94,1,0 -710,71,1,46.245,48,46.245,46.245,2,2,.,.,.,2,0,1,247.4055328965615,5.511,0,0,5,1,1,1000,1000,4,0,0,1,39,5,78.7,94,1,0 -711,72,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,1,3,52,5,90.4,89,2,1 -712,72,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,1,3,52,5,90.4,89,2,0 -713,72,1,0.237,0.25,0.237,0.237,1,1,.,.,.,2,0,1,1233.7904760892004,7.118,0,0,5,1,1,1000,1000,4,0,1,3,52,5,90.4,89,2,0 -714,72,1,0.452,0.5,0.452,0.452,1,1,.,.,.,2,0,1,1853.929738355565,7.525,0,0,5,1,1,1000,1000,4,0,1,3,52,5,90.4,89,2,0 -715,72,1,1.028,1,1.028,1.028,1,1,.,.,.,2,0,1,2789.8765299433944,7.934,0,0,5,1,1,1000,1000,4,0,1,3,52,5,90.4,89,2,0 -716,72,1,4.162,4,4.162,4.162,1,1,.,.,.,2,0,1,3022.5754657936845,8.014,0,0,5,1,1,1000,1000,4,0,1,3,52,5,90.4,89,2,0 -717,72,1,7.534,8,7.534,7.534,1,1,.,.,.,2,0,1,2866.5584916841326,7.961,0,0,5,1,1,1000,1000,4,0,1,3,52,5,90.4,89,2,0 -718,72,1,12.635,12,12.635,12.635,1,1,.,.,.,2,0,1,2058.1998477724833,7.63,0,0,5,1,1,1000,1000,4,0,1,3,52,5,90.4,89,2,0 -719,72,1,25.866,24,25.866,25.866,2,1,.,.,.,2,0,1,508.1160341737293,6.231,0,0,5,1,1,1000,1000,4,0,1,3,52,5,90.4,89,2,0 -720,72,1,51.991,48,51.991,51.991,3,2,.,.,.,2,0,1,149.12318128239798,5.005,0,0,5,1,1,1000,1000,4,0,1,3,52,5,90.4,89,2,0 -721,73,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,1,3,53,5,78.9,72,2,1 -722,73,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,1,3,53,5,78.9,72,2,0 -723,73,1,0.235,0.25,0.235,0.235,1,1,.,.,.,2,0,1,1224.2606886919743,7.11,0,0,5,1,1,1000,1000,4,0,1,3,53,5,78.9,72,2,0 -724,73,1,0.516,0.5,0.516,0.516,1,1,.,.,.,2,0,1,2020.3363829118796,7.611,0,0,5,1,1,1000,1000,4,0,1,3,53,5,78.9,72,2,0 -725,73,1,0.993,1,0.993,0.993,1,1,.,.,.,2,0,1,4449.245197070604,8.4,0,0,5,1,1,1000,1000,4,0,1,3,53,5,78.9,72,2,0 -726,73,1,3.682,4,3.682,3.682,1,1,.,.,.,2,0,1,3254.676859562131,8.088,0,0,5,1,1,1000,1000,4,0,1,3,53,5,78.9,72,2,0 -727,73,1,8.62,8,8.62,8.62,1,1,.,.,.,2,0,1,2293.244727802458,7.738,0,0,5,1,1,1000,1000,4,0,1,3,53,5,78.9,72,2,0 -728,73,1,12.649,12,12.649,12.649,1,1,.,.,.,2,0,1,1015.378527428847,6.923,0,0,5,1,1,1000,1000,4,0,1,3,53,5,78.9,72,2,0 -729,73,1,24.792,24,24.792,24.792,2,1,.,.,.,2,0,1,461.8442853547288,6.135,0,0,5,1,1,1000,1000,4,0,1,3,53,5,78.9,72,2,0 -730,73,1,44.395,48,44.395,44.395,2,2,.,.,.,2,0,1,115.56337388107191,4.75,0,0,5,1,1,1000,1000,4,0,1,3,53,5,78.9,72,2,0 -731,74,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,1,3,21,5,111.5,77,2,1 -732,74,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,1,3,21,5,111.5,77,2,0 -733,74,1,0.253,0.25,0.253,0.253,1,1,.,.,.,2,0,1,1332.9577666991793,7.195,0,0,5,1,1,1000,1000,4,0,1,3,21,5,111.5,77,2,0 -734,74,1,0.497,0.5,0.497,0.497,1,1,.,.,.,2,0,1,2431.0823735274057,7.796,0,0,5,1,1,1000,1000,4,0,1,3,21,5,111.5,77,2,0 -735,74,1,1.033,1,1.033,1.033,1,1,.,.,.,2,0,1,2909.181828060483,7.976,0,0,5,1,1,1000,1000,4,0,1,3,21,5,111.5,77,2,0 -736,74,1,4.201,4,4.201,4.201,1,1,.,.,.,2,0,1,3900.9370125857467,8.269,0,0,5,1,1,1000,1000,4,0,1,3,21,5,111.5,77,2,0 -737,74,1,8.436,8,8.436,8.436,1,1,.,.,.,2,0,1,2783.654113828685,7.932,0,0,5,1,1,1000,1000,4,0,1,3,21,5,111.5,77,2,0 -738,74,1,12.634,12,12.634,12.634,1,1,.,.,.,2,0,1,1825.971015296924,7.51,0,0,5,1,1,1000,1000,4,0,1,3,21,5,111.5,77,2,0 -739,74,1,26.223,24,26.223,26.223,2,1,.,.,.,2,0,1,931.4967530242837,6.837,0,0,5,1,1,1000,1000,4,0,1,3,21,5,111.5,77,2,0 -740,74,1,49.735,48,49.735,49.735,3,2,.,.,.,2,0,1,649.495547026835,6.476,0,0,5,1,1,1000,1000,4,0,1,3,21,5,111.5,77,2,0 -741,75,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,20,5,77,78,2,1 -742,75,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,20,5,77,78,2,0 -743,75,1,0.256,0.25,0.256,0.256,1,1,.,.,.,2,0,1,1132.8423244472767,7.032,0,0,5,1,1,1000,1000,4,0,0,1,20,5,77,78,2,0 -744,75,1,0.518,0.5,0.518,0.518,1,1,.,.,.,2,0,1,1905.760898391289,7.553,0,0,5,1,1,1000,1000,4,0,0,1,20,5,77,78,2,0 -745,75,1,0.931,1,0.931,0.931,1,1,.,.,.,2,0,1,2746.549556564803,7.918,0,0,5,1,1,1000,1000,4,0,0,1,20,5,77,78,2,0 -746,75,1,4.342,4,4.342,4.342,1,1,.,.,.,2,0,1,2894.403560082858,7.971,0,0,5,1,1,1000,1000,4,0,0,1,20,5,77,78,2,0 -747,75,1,8.8,8,8.8,8.8,1,1,.,.,.,2,0,1,2209.0355182822836,7.7,0,0,5,1,1,1000,1000,4,0,0,1,20,5,77,78,2,0 -748,75,1,11.12,12,11.12,11.12,1,1,.,.,.,2,0,1,1336.2595985325108,7.198,0,0,5,1,1,1000,1000,4,0,0,1,20,5,77,78,2,0 -749,75,1,22.11,24,22.11,22.11,1,1,.,.,.,2,0,1,523.8976567399562,6.261,0,0,5,1,1,1000,1000,4,0,0,1,20,5,77,78,2,0 -750,75,1,50.519,48,50.519,50.519,3,2,.,.,.,2,0,1,125.39115876428644,4.831,0,0,5,1,1,1000,1000,4,0,0,1,20,5,77,78,2,0 -751,76,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,26,5,52.3,109,1,1 -752,76,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,26,5,52.3,109,1,0 -753,76,1,0.256,0.25,0.256,0.256,1,1,.,.,.,2,0,1,2716.815649324218,7.907,0,0,5,1,1,1000,1000,4,0,0,1,26,5,52.3,109,1,0 -754,76,1,0.532,0.5,0.532,0.532,1,1,.,.,.,2,0,1,2695.7623081194065,7.899,0,0,5,1,1,1000,1000,4,0,0,1,26,5,52.3,109,1,0 -755,76,1,0.936,1,0.936,0.936,1,1,.,.,.,2,0,1,2509.052888087842,7.828,0,0,5,1,1,1000,1000,4,0,0,1,26,5,52.3,109,1,0 -756,76,1,3.869,4,3.869,3.869,1,1,.,.,.,2,0,1,3107.259044385997,8.041,0,0,5,1,1,1000,1000,4,0,0,1,26,5,52.3,109,1,0 -757,76,1,7.458,8,7.458,7.458,1,1,.,.,.,2,0,1,1819.4903321731474,7.506,0,0,5,1,1,1000,1000,4,0,0,1,26,5,52.3,109,1,0 -758,76,1,12.53,12,12.53,12.53,1,1,.,.,.,2,0,1,1757.9166591076375,7.472,0,0,5,1,1,1000,1000,4,0,0,1,26,5,52.3,109,1,0 -759,76,1,23.647,24,23.647,23.647,1,1,.,.,.,2,0,1,626.0727526851555,6.439,0,0,5,1,1,1000,1000,4,0,0,1,26,5,52.3,109,1,0 -760,76,1,44.834,48,44.834,44.834,2,2,.,.,.,2,0,1,181.41209788516744,5.201,0,0,5,1,1,1000,1000,4,0,0,1,26,5,52.3,109,1,0 -761,77,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,1,3,27,5,88.2,83,2,1 -762,77,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,1,3,27,5,88.2,83,2,0 -763,77,1,0.238,0.25,0.238,0.238,1,1,.,.,.,2,0,1,776.2338644366163,6.654,0,0,5,1,1,1000,1000,4,0,1,3,27,5,88.2,83,2,0 -764,77,1,0.457,0.5,0.457,0.457,1,1,.,.,.,2,0,1,964.4627442019198,6.872,0,0,5,1,1,1000,1000,4,0,1,3,27,5,88.2,83,2,0 -765,77,1,1.087,1,1.087,1.087,1,1,.,.,.,2,0,1,2511.337997759726,7.829,0,0,5,1,1,1000,1000,4,0,1,3,27,5,88.2,83,2,0 -766,77,1,3.981,4,3.981,3.981,1,1,.,.,.,2,0,1,3604.2356046739883,8.19,0,0,5,1,1,1000,1000,4,0,1,3,27,5,88.2,83,2,0 -767,77,1,8.609,8,8.609,8.609,1,1,.,.,.,2,0,1,2494.5585014879166,7.822,0,0,5,1,1,1000,1000,4,0,1,3,27,5,88.2,83,2,0 -768,77,1,11.146,12,11.146,11.146,1,1,.,.,.,2,0,1,2190.755784047479,7.692,0,0,5,1,1,1000,1000,4,0,1,3,27,5,88.2,83,2,0 -769,77,1,23.961,24,23.961,23.961,1,1,.,.,.,2,0,1,899.9433582219998,6.802,0,0,5,1,1,1000,1000,4,0,1,3,27,5,88.2,83,2,0 -770,77,1,47.428,48,47.428,47.428,2,2,.,.,.,2,0,1,167.21659192637665,5.119,0,0,5,1,1,1000,1000,4,0,1,3,27,5,88.2,83,2,0 -771,78,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,1,3,19,5,69.6,114,1,1 -772,78,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,1,3,19,5,69.6,114,1,0 -773,78,1,0.25,0.25,0.25,0.25,1,1,.,.,.,2,0,1,1181.171026636279,7.074,0,0,5,1,1,1000,1000,4,0,1,3,19,5,69.6,114,1,0 -774,78,1,0.506,0.5,0.506,0.506,1,1,.,.,.,2,0,1,3057.1207578876188,8.025,0,0,5,1,1,1000,1000,4,0,1,3,19,5,69.6,114,1,0 -775,78,1,0.976,1,0.976,0.976,1,1,.,.,.,2,0,1,3632.6237326923565,8.198,0,0,5,1,1,1000,1000,4,0,1,3,19,5,69.6,114,1,0 -776,78,1,3.774,4,3.774,3.774,1,1,.,.,.,2,0,1,2596.1369320358026,7.862,0,0,5,1,1,1000,1000,4,0,1,3,19,5,69.6,114,1,0 -777,78,1,7.419,8,7.419,7.419,1,1,.,.,.,2,0,1,1607.107472139053,7.382,0,0,5,1,1,1000,1000,4,0,1,3,19,5,69.6,114,1,0 -778,78,1,13.076,12,13.076,13.076,1,1,.,.,.,2,0,1,1175.673159075275,7.07,0,0,5,1,1,1000,1000,4,0,1,3,19,5,69.6,114,1,0 -779,78,1,21.647,24,21.647,21.647,1,1,.,.,.,2,0,1,484.1178247323139,6.182,0,0,5,1,1,1000,1000,4,0,1,3,19,5,69.6,114,1,0 -780,78,1,48.855,48,48.855,48.855,3,2,.,.,.,2,0,1,65.04579398218401,4.175,0,0,5,1,1,1000,1000,4,0,1,3,19,5,69.6,114,1,0 -781,79,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,38,5,46.4,106,1,1 -782,79,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,38,5,46.4,106,1,0 -783,79,1,0.259,0.25,0.259,0.259,1,1,.,.,.,2,0,1,1706.2082476938087,7.442,0,0,5,1,1,1000,1000,4,0,0,1,38,5,46.4,106,1,0 -784,79,1,0.51,0.5,0.51,0.51,1,1,.,.,.,2,0,1,3683.8036945926683,8.212,0,0,5,1,1,1000,1000,4,0,0,1,38,5,46.4,106,1,0 -785,79,1,0.911,1,0.911,0.911,1,1,.,.,.,2,0,1,1797.0528333048073,7.494,0,0,5,1,1,1000,1000,4,0,0,1,38,5,46.4,106,1,0 -786,79,1,3.612,4,3.612,3.612,1,1,.,.,.,2,0,1,3237.8999724003634,8.083,0,0,5,1,1,1000,1000,4,0,0,1,38,5,46.4,106,1,0 -787,79,1,7.854,8,7.854,7.854,1,1,.,.,.,2,0,1,1935.2569548745782,7.568,0,0,5,1,1,1000,1000,4,0,0,1,38,5,46.4,106,1,0 -788,79,1,11.075,12,11.075,11.075,1,1,.,.,.,2,0,1,1271.5185584722992,7.148,0,0,5,1,1,1000,1000,4,0,0,1,38,5,46.4,106,1,0 -789,79,1,26.216,24,26.216,26.216,2,1,.,.,.,2,0,1,235.5032086502458,5.462,0,0,5,1,1,1000,1000,4,0,0,1,38,5,46.4,106,1,0 -790,79,1,46.972,48,46.972,46.972,2,2,.,.,.,2,0,1,20.881913229987706,3.039,0,0,5,1,1,1000,1000,4,0,0,1,38,5,46.4,106,1,0 -791,80,1,0,0,0,0,1,1,.,.,.,2,0,1,.,.,1,.,5,1,1,1000,1000,4,0,0,1,44,5,52.7,83,2,1 -792,80,1,0,0,0,0,1,1,1000,.,.,1,1,0,.,.,1,0,5,1,1,1000,1000,4,0,0,1,44,5,52.7,83,2,0 -793,80,1,0.232,0.25,0.232,0.232,1,1,.,.,.,2,0,1,1066.8279759953161,6.972,0,0,5,1,1,1000,1000,4,0,0,1,44,5,52.7,83,2,0 -794,80,1,0.542,0.5,0.542,0.542,1,1,.,.,.,2,0,1,4998.834848168029,8.517,0,0,5,1,1,1000,1000,4,0,0,1,44,5,52.7,83,2,0 -795,80,1,1.079,1,1.079,1.079,1,1,.,.,.,2,0,1,6495.09106416749,8.779,0,0,5,1,1,1000,1000,4,0,0,1,44,5,52.7,83,2,0 -796,80,1,4.1,4,4.1,4.1,1,1,.,.,.,2,0,1,4796.368834030873,8.476,0,0,5,1,1,1000,1000,4,0,0,1,44,5,52.7,83,2,0 -797,80,1,7.209,8,7.209,7.209,1,1,.,.,.,2,0,1,3361.382006860858,8.12,0,0,5,1,1,1000,1000,4,0,0,1,44,5,52.7,83,2,0 -798,80,1,13.187,12,13.187,13.187,1,1,.,.,.,2,0,1,2291.7225610342075,7.737,0,0,5,1,1,1000,1000,4,0,0,1,44,5,52.7,83,2,0 -799,80,1,25.84,24,25.84,25.84,2,1,.,.,.,2,0,1,703.0648954331083,6.555,0,0,5,1,1,1000,1000,4,0,0,1,44,5,52.7,83,2,0 -800,80,1,43.822,48,43.822,43.822,2,2,.,.,.,2,0,1,341.66481208668955,5.834,0,0,5,1,1,1000,1000,4,0,0,1,44,5,52.7,83,2,0 diff --git a/inst/extdata/mod/1001.mod b/inst/extdata/mod/1001.mod index fe1d6232..d7940039 100644 --- a/inst/extdata/mod/1001.mod +++ b/inst/extdata/mod/1001.mod @@ -52,8 +52,8 @@ $THETA 1 FIX ;F1 (fraction) $OMEGA - 0.1 ;OM1 CL :EXP - 0.1 ;OM2 VC :EXP + 0.1 ;OM1 CL/F :EXP + 0.1 ;OM2 VC/F :EXP 0.1 ;OM3 KA :EXP diff --git a/inst/extdata/models/onecmt/run003.mod b/inst/extdata/models/onecmt/run003.mod index c081c377..e181b798 100644 --- a/inst/extdata/models/onecmt/run003.mod +++ b/inst/extdata/models/onecmt/run003.mod @@ -32,7 +32,7 @@ $THETA $OMEGA BLOCK(2) 0.1 ;OM1 TVCL :EXP -0.0001 ;OM1,2 TVCL:TVV :EXP +0.0001 ;OM1,2 TVCL,TVV :EXP 0.1 ;OM2 TVV :EXP $OMEGA 0.1 ;OM3 TVKA :EXP diff --git a/inst/extdata/models/onecmt/run003/run003.lst b/inst/extdata/models/onecmt/run003/run003.lst index 9a7db0f7..39cfe3ae 100644 --- a/inst/extdata/models/onecmt/run003/run003.lst +++ b/inst/extdata/models/onecmt/run003/run003.lst @@ -33,7 +33,7 @@ $THETA $OMEGA BLOCK(2) 0.1 ;OM1 TVCL :EXP -0.0001 ;OM1,2 TVCL:TVV :EXP +0.0001 ;OM1,2 TVCL,TVV :EXP 0.1 ;OM2 TVV :EXP $OMEGA 0.1 ;OM3 TVKA :EXP diff --git a/inst/extdata/models/onecmt/run003/run003.mod b/inst/extdata/models/onecmt/run003/run003.mod index c7cdffbf..4d4f693b 100644 --- a/inst/extdata/models/onecmt/run003/run003.mod +++ b/inst/extdata/models/onecmt/run003/run003.mod @@ -32,7 +32,7 @@ $THETA $OMEGA BLOCK(2) 0.1 ;OM1 TVCL :EXP -0.0001 ;OM1,2 TVCL:TVV :EXP +0.0001 ;OM1,2 TVCL,TVV :EXP 0.1 ;OM2 TVV :EXP $OMEGA 0.1 ;OM3 TVKA :EXP diff --git a/inst/extdata/models/onecmt/run003b1.mod b/inst/extdata/models/onecmt/run003b1.mod index b3567bc8..4d692a4e 100644 --- a/inst/extdata/models/onecmt/run003b1.mod +++ b/inst/extdata/models/onecmt/run003b1.mod @@ -34,7 +34,7 @@ $THETA $OMEGA BLOCK(2) 0.103 ;OM1 TVCL :EXP -0.00009 ;OM1,2 TVCL:TVV :EXP +0.00009 ;OM1,2 TVCL,TVV :EXP 0.109 ;OM2 TVV :EXP $OMEGA 0.099 ;OM3 TVKA :EXP diff --git a/inst/extdata/models/onecmt/run003b1/run003b1.lst b/inst/extdata/models/onecmt/run003b1/run003b1.lst index 2331bc8d..d3e86088 100644 --- a/inst/extdata/models/onecmt/run003b1/run003b1.lst +++ b/inst/extdata/models/onecmt/run003b1/run003b1.lst @@ -35,7 +35,7 @@ $THETA $OMEGA BLOCK(2) 0.103 ;OM1 TVCL :EXP -0.00009 ;OM1,2 TVCL:TVV :EXP +0.00009 ;OM1,2 TVCL,TVV :EXP 0.109 ;OM2 TVV :EXP $OMEGA 0.099 ;OM3 TVKA :EXP diff --git a/inst/extdata/models/onecmt/run003b1/run003b1.mod b/inst/extdata/models/onecmt/run003b1/run003b1.mod index a5d28142..ab99a33c 100644 --- a/inst/extdata/models/onecmt/run003b1/run003b1.mod +++ b/inst/extdata/models/onecmt/run003b1/run003b1.mod @@ -34,7 +34,7 @@ $THETA $OMEGA BLOCK(2) 0.103 ;OM1 TVCL :EXP -0.00009 ;OM1,2 TVCL:TVV :EXP +0.00009 ;OM1,2 TVCL,TVV :EXP 0.109 ;OM2 TVV :EXP $OMEGA 0.099 ;OM3 TVKA :EXP diff --git a/inst/extdata/models/onecmt/run003b2.mod b/inst/extdata/models/onecmt/run003b2.mod index fb6bbafa..8282c25f 100644 --- a/inst/extdata/models/onecmt/run003b2.mod +++ b/inst/extdata/models/onecmt/run003b2.mod @@ -32,7 +32,7 @@ $THETA $OMEGA BLOCK(2) 0.122 ;OM1 TVCL :EXP -0.074543 ;OM1,2 TVCL:TVV :EXP +0.074543 ;OM1,2 TVCL,TVV :EXP 0.124 ;OM2 TVV :EXP $OMEGA 0.122 ;OM3 TVKA :EXP diff --git a/inst/extdata/models/onecmt/run004.mod b/inst/extdata/models/onecmt/run004.mod index 37bce9d6..a14b6777 100644 --- a/inst/extdata/models/onecmt/run004.mod +++ b/inst/extdata/models/onecmt/run004.mod @@ -32,7 +32,7 @@ $THETA $OMEGA BLOCK(2) 0.1 ;OM1 TVCL :EXP -0.0001 ;OM1,2 TVCL:TVV :EXP +0.0001 ;OM1,2 TVCL,TVV :EXP 0.1 ;OM2 TVV :EXP $OMEGA 0.1 ;OM3 TVKA :EXP diff --git a/inst/pharos.toml b/inst/pharos.toml index f41f9d85..4bf966f6 100644 --- a/inst/pharos.toml +++ b/inst/pharos.toml @@ -22,7 +22,7 @@ num_cpus = 4 timeout = 2147483647 [nonmem.comments] -type = "type1" +type = "type2" error_on_invalid = false [nonmem.summary] diff --git a/man/copy_model.Rd b/man/copy_model.Rd index 2642f6d0..1a17d2a3 100644 --- a/man/copy_model.Rd +++ b/man/copy_model.Rd @@ -13,7 +13,7 @@ copy_model( jitter = NULL, jitter_excluded = NULL, seed = NULL, - description = NULL, + description, based_on = NULL, no_metadata = FALSE ) diff --git a/man/get_model_comment_info.Rd b/man/get_model_comment_info.Rd new file mode 100644 index 00000000..3de6ccb5 --- /dev/null +++ b/man/get_model_comment_info.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/extendr-wrappers.R +\name{get_model_comment_info} +\alias{get_model_comment_info} +\title{Build per-parameter comment info from a model object (internal)} +\usage{ +get_model_comment_info(model) +} +\arguments{ +\item{model}{hyperion_nonmem_model object from read_model()} +} +\value{ +list with \code{thetas}, \code{omegas}, \code{sigmas} entries; each is a list of +length-2 lists \verb{(coordinate, info)} in numeric coordinate order. +} +\description{ +Build per-parameter comment info from a model object (internal) +} +\keyword{internal} diff --git a/man/get_model_parameter_info.Rd b/man/get_model_parameter_info.Rd index bbc1236d..f7ed6b34 100644 --- a/man/get_model_parameter_info.Rd +++ b/man/get_model_parameter_info.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/comments-parsing.R +% Please edit documentation in R/parameter-info.R \name{get_model_parameter_info} \alias{get_model_parameter_info} \title{Extract all parameter comments from a model as ModelComments object} diff --git a/man/map_parameterization.Rd b/man/map_parameterization.Rd new file mode 100644 index 00000000..e0557448 --- /dev/null +++ b/man/map_parameterization.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/extendr-wrappers.R +\name{map_parameterization} +\alias{map_parameterization} +\title{Canonicalize a parameterization alias to its PascalCase form.} +\usage{ +map_parameterization(raw) +} +\arguments{ +\item{raw}{Parameterization alias (e.g. \code{"EXP"}, \code{"lognormal"}, \code{"PROP"}).} +} +\value{ +Canonical name (\code{"LogNormal"}, \code{"Proportional"}, ...) or \code{NULL} +if \code{raw} is not a recognized alias. +} +\description{ +Canonicalize a parameterization alias to its PascalCase form. +} +\keyword{internal} diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index bd1e51bc..b1c1b2cf 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -141,7 +141,7 @@ dependencies = [ [[package]] name = "config" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=metadata-update#aba478226b4438cabb5daf22b67b9a60d4ffd230" +source = "git+https://github.com/a2-ai/pharos?branch=parsed-comment-pub#43faef861ea3ac7d42d6b7994a62d31d6e8802bc" dependencies = [ "anyhow", "fs-err", @@ -284,7 +284,8 @@ dependencies = [ [[package]] name = "extendr-api" version = "0.9.0" -source = "git+https://github.com/extendr/extendr?branch=main#b0cb8a811977f2a93147744f24fe22c5e1c630d9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "803569de0d273b4bf281871046a7d63a23cc12776bdb5b63de5c1e81aae30728" dependencies = [ "extendr-ffi", "extendr-macros", @@ -297,12 +298,14 @@ dependencies = [ [[package]] name = "extendr-ffi" version = "0.9.0" -source = "git+https://github.com/extendr/extendr?branch=main#b0cb8a811977f2a93147744f24fe22c5e1c630d9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ba82ddd48e85202654997b81e4b1d39c0c54b5dcd7cae92705f807bf528efcf" [[package]] name = "extendr-macros" version = "0.9.0" -source = "git+https://github.com/extendr/extendr?branch=main#b0cb8a811977f2a93147744f24fe22c5e1c630d9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba8fad8d2a0d0651b1947042cf3a8beddc73d39cec3485b200fdfd24cb3bb6aa" dependencies = [ "lazy_static", "proc-macro2", @@ -343,6 +346,30 @@ dependencies = [ "autocfg", ] +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -482,6 +509,7 @@ dependencies = [ name = "hyperion-nonmem" version = "0.1.0" dependencies = [ + "anyhow", "config", "extendr-api", "fs-err", @@ -626,10 +654,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.95" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -711,12 +741,11 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "nonmem" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=metadata-update#aba478226b4438cabb5daf22b67b9a60d4ffd230" +source = "git+https://github.com/a2-ai/pharos?branch=parsed-comment-pub#43faef861ea3ac7d42d6b7994a62d31d6e8802bc" dependencies = [ "anyhow", "blake3", "config", - "distrs", "fs-err", "glob", "jiff", @@ -737,11 +766,12 @@ dependencies = [ [[package]] name = "nonmem-parser" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=metadata-update#aba478226b4438cabb5daf22b67b9a60d4ffd230" +source = "git+https://github.com/a2-ai/pharos?branch=parsed-comment-pub#43faef861ea3ac7d42d6b7994a62d31d6e8802bc" dependencies = [ "anyhow", "distrs", "fs-err", + "log", "logos", "rand 0.9.4", "regex", @@ -875,6 +905,12 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + [[package]] name = "portable-atomic" version = "1.13.1" @@ -1089,7 +1125,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=metadata-update#aba478226b4438cabb5daf22b67b9a60d4ffd230" +source = "git+https://github.com/a2-ai/pharos?branch=parsed-comment-pub#43faef861ea3ac7d42d6b7994a62d31d6e8802bc" dependencies = [ "anyhow", "config", @@ -1207,6 +1243,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + [[package]] name = "slug" version = "0.1.6" @@ -1335,7 +1377,7 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utils" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=metadata-update#aba478226b4438cabb5daf22b67b9a60d4ffd230" +source = "git+https://github.com/a2-ai/pharos?branch=parsed-comment-pub#43faef861ea3ac7d42d6b7994a62d31d6e8802bc" dependencies = [ "anyhow", "fs-err", @@ -1386,9 +1428,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" dependencies = [ "cfg-if", "once_cell", @@ -1399,9 +1441,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1409,9 +1451,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" dependencies = [ "bumpalo", "proc-macro2", @@ -1422,9 +1464,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" dependencies = [ "unicode-ident", ] diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index ca7ad3de..2a463a3e 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -35,13 +35,13 @@ serde = { workspace = true } [workspace.dependencies] # R Integration -extendr-api = { git = "https://github.com/extendr/extendr", branch = "main", features = ["serde"] } +extendr-api = { version = "0.9.0", features = ["serde"] } # Pharos components -nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "metadata-update" } -nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", branch = "metadata-update" } -config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "metadata-update" } -scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "metadata-update" } +nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "parsed-comment-pub" } +nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", branch = "parsed-comment-pub" } +config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "parsed-comment-pub" } +scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "parsed-comment-pub" } # Core utilities anyhow = "1.0.100" diff --git a/src/rust/nonmem/Cargo.toml b/src/rust/nonmem/Cargo.toml index 865101bd..07edaf8a 100644 --- a/src/rust/nonmem/Cargo.toml +++ b/src/rust/nonmem/Cargo.toml @@ -14,6 +14,7 @@ nmparser = { workspace = true } config = { workspace = true } # Core utilities +anyhow = { workspace = true } fs-err = { workspace = true } serde = { workspace = true } diff --git a/src/rust/nonmem/src/model/comment_info.rs b/src/rust/nonmem/src/model/comment_info.rs new file mode 100644 index 00000000..3340d963 --- /dev/null +++ b/src/rust/nonmem/src/model/comment_info.rs @@ -0,0 +1,215 @@ +use std::collections::BTreeMap; + +use extendr_api::Result; +use extendr_api::prelude::*; +use extendr_api::serializer::to_robj; +use serde::Serialize; + +use nmparser::{ + Model, OmegaSigmaEntry, OmegaSigmaParam, ParameterOrdering, ParsedOmegaComment, + ParsedRaneffComment, ParsedSigmaComment, ParsedThetaComment, Transform, Type1Theta, +}; + +use crate::model::parameters::compare_param_names; +use crate::model::robj_to_model; +use hyperion_core::ResultExt; + +#[derive(Debug, Clone, Serialize)] +pub struct ThetaCommentInfo { + pub name: Option, + pub unit: Option, + pub parameterization: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct OmegaCommentInfo { + pub name: Option, + pub associated_theta: Vec, + pub parameterization: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct SigmaCommentInfo { + pub name: Option, + pub unit: Option, + pub parameterization: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct ModelCommentInfo { + pub thetas: Vec<(String, ThetaCommentInfo)>, + pub omegas: Vec<(String, OmegaCommentInfo)>, + pub sigmas: Vec<(String, SigmaCommentInfo)>, +} + +/// Canonicalize a Type1 raw parameterization string via pharos's `Transform::FromStr`. +fn map_parameterization_str(raw: &str) -> Option { + raw.parse::().ok().map(|t| t.to_string()) +} + +fn build_theta_info(parsed: Option<&ParsedThetaComment>) -> ThetaCommentInfo { + match parsed { + Some(ParsedThetaComment::Type1(Type1Theta::WithUnit { + parameter, + unit, + parametrization, + })) => ThetaCommentInfo { + name: Some(parameter.clone()), + unit: Some(unit.clone()), + parameterization: parametrization + .as_deref() + .and_then(map_parameterization_str), + }, + Some(ParsedThetaComment::Type1(Type1Theta::Covariate { parameter })) => ThetaCommentInfo { + name: Some(parameter.clone()), + unit: None, + parameterization: None, + }, + Some(ParsedThetaComment::Type1(Type1Theta::Type { + typ, + parameterization, + })) => ThetaCommentInfo { + name: Some(typ.clone()), + unit: None, + parameterization: map_parameterization_str(parameterization), + }, + Some(ParsedThetaComment::Type2(t)) => ThetaCommentInfo { + name: Some(t.name.clone()), + unit: t.unit.clone(), + parameterization: t.parameterization.as_ref().map(ToString::to_string), + }, + None => ThetaCommentInfo { + name: None, + unit: None, + parameterization: None, + }, + } +} + +fn build_omega_info(param: &OmegaSigmaParam) -> OmegaCommentInfo { + let inner = match param.parsed_comment.as_ref() { + Some(ParsedRaneffComment::Omega(o)) => Some(o), + Some(ParsedRaneffComment::Sigma(_)) => { + panic!("pharos invariant: omega block parameter has Sigma parsed comment") + } + None => None, + }; + + match inner { + Some(ParsedOmegaComment::Type1(o)) => OmegaCommentInfo { + name: Some(o.name.clone()), + associated_theta: vec![o.theta_name.clone()], + parameterization: map_parameterization_str(&o.parameterization), + }, + Some(ParsedOmegaComment::Type2(o)) => OmegaCommentInfo { + name: Some(o.name.clone()), + associated_theta: o.raw_theta_refs.clone(), + parameterization: o.parameterization.as_ref().map(ToString::to_string), + }, + None => OmegaCommentInfo { + name: None, + associated_theta: Vec::new(), + parameterization: None, + }, + } +} + +fn build_sigma_info(param: &OmegaSigmaParam) -> SigmaCommentInfo { + let inner = match param.parsed_comment.as_ref() { + Some(ParsedRaneffComment::Sigma(s)) => Some(s), + Some(ParsedRaneffComment::Omega(_)) => { + panic!("pharos invariant: sigma block parameter has Omega parsed comment") + } + None => None, + }; + + match inner { + Some(ParsedSigmaComment::Type1(s)) => SigmaCommentInfo { + name: Some(s.name.clone()), + unit: None, + parameterization: s + .parameterization + .as_deref() + .and_then(map_parameterization_str), + }, + Some(ParsedSigmaComment::Type2(s)) => SigmaCommentInfo { + name: Some(s.name.clone()), + unit: s.unit.clone(), + parameterization: s.parameterization.as_ref().map(ToString::to_string), + }, + None => SigmaCommentInfo { + name: None, + unit: None, + parameterization: None, + }, + } +} + +fn sort_entries(entries: BTreeMap) -> Vec<(String, T)> { + let mut out: Vec<(String, T)> = entries.into_iter().collect(); + out.sort_by(|a, b| compare_param_names(&a.0, &b.0)); + out +} + +/// Walk a model's parameters and produce per-parameter comment info. +pub fn build(model: &Model) -> anyhow::Result { + let mut thetas: BTreeMap = BTreeMap::new(); + for (i, theta) in model.thetas.iter().enumerate() { + let key = format!("THETA{}", i + 1); + thetas.insert(key, build_theta_info(theta.parsed_comment.as_ref())); + } + + let omega_entries: Vec = + model.get_omega_parameters(ParameterOrdering::RowMajor)?; + let mut omegas: BTreeMap = BTreeMap::new(); + for entry in omega_entries { + let info = build_omega_info(&entry.parameter); + omegas.insert(entry.param_name, info); + } + + let sigma_entries: Vec = + model.get_sigma_parameters(ParameterOrdering::RowMajor)?; + let mut sigmas: BTreeMap = BTreeMap::new(); + for entry in sigma_entries { + let info = build_sigma_info(&entry.parameter); + sigmas.insert(entry.param_name, info); + } + + Ok(ModelCommentInfo { + thetas: sort_entries(thetas), + omegas: sort_entries(omegas), + sigmas: sort_entries(sigmas), + }) +} + +/// Build per-parameter comment info from a model object (internal) +/// +/// @param model hyperion_nonmem_model object from read_model() +/// +/// @return list with `thetas`, `omegas`, `sigmas` entries; each is a list of +/// length-2 lists `(coordinate, info)` in numeric coordinate order. +/// @keywords internal +#[extendr] +pub fn get_model_comment_info(model: Robj) -> Result { + let model = robj_to_model(&model)?; + let info = build(&model).map_to_extendr_err("Failed to build comment info")?; + to_robj(&info).map_to_extendr_err("Failed to serialize comment info") +} + +/// Canonicalize a parameterization alias to its PascalCase form. +/// +/// @param raw Parameterization alias (e.g. `"EXP"`, `"lognormal"`, `"PROP"`). +/// @return Canonical name (`"LogNormal"`, `"Proportional"`, ...) or `NULL` +/// if `raw` is not a recognized alias. +/// @keywords internal +#[extendr] +pub fn map_parameterization(raw: &str) -> Option { + map_parameterization_str(raw) +} + +extendr_module! { + mod comment_info; + + fn get_model_comment_info; + fn map_parameterization; +} diff --git a/src/rust/nonmem/src/model/copy.rs b/src/rust/nonmem/src/model/copy.rs index b4d19615..67c90a1e 100644 --- a/src/rust/nonmem/src/model/copy.rs +++ b/src/rust/nonmem/src/model/copy.rs @@ -113,7 +113,7 @@ pub fn copy_model_wrap( // This should move to Option #[extendr(default = "NULL")] jitter_excluded: Option<&Robj>, #[extendr(default = "NULL")] seed: Option, - #[extendr(default = "NULL")] description: String, + description: String, #[extendr(default = "NULL")] based_on: Option>, #[extendr(default = "FALSE")] no_metadata: bool, ) -> Result<()> { diff --git a/src/rust/nonmem/src/model/mod.rs b/src/rust/nonmem/src/model/mod.rs index 43f73f52..76e45d69 100644 --- a/src/rust/nonmem/src/model/mod.rs +++ b/src/rust/nonmem/src/model/mod.rs @@ -12,6 +12,7 @@ use crate::utils::{find_output_file, get_comment_type, to_config_relative, valid use hyperion_core::{ResultExt, extendr_err}; pub mod check; +pub mod comment_info; pub mod copy; pub mod lineage; pub mod metadata; @@ -140,6 +141,7 @@ extendr_module! { use check; use lineage; use parameters; + use comment_info; use metadata; use run_status; diff --git a/src/rust/nonmem/src/model/parameters.rs b/src/rust/nonmem/src/model/parameters.rs index 854f144b..4646e85b 100644 --- a/src/rust/nonmem/src/model/parameters.rs +++ b/src/rust/nonmem/src/model/parameters.rs @@ -65,7 +65,7 @@ fn extract_param_sort_key(name: &str) -> (u32, u32, u8) { } /// Compare two parameter names for numeric sorting. -fn compare_param_names(a: &str, b: &str) -> Ordering { +pub(crate) fn compare_param_names(a: &str, b: &str) -> Ordering { let key_a = extract_param_sort_key(a); let key_b = extract_param_sort_key(b); diff --git a/src/rust/nonmem/src/output_files/transforms.rs b/src/rust/nonmem/src/output_files/transforms.rs index bafce729..0570de52 100644 --- a/src/rust/nonmem/src/output_files/transforms.rs +++ b/src/rust/nonmem/src/output_files/transforms.rs @@ -4,8 +4,8 @@ use extendr_api::scalar::Rfloat; use std::str::FromStr; +use nmparser::Transform; use nonmem::output_files::ext::ParameterType; -use nonmem::transforms::Transform; use hyperion_core::extendr_err; diff --git a/tests/testthat/_snaps/hyperion-model-knit/model-knit-1001.html b/tests/testthat/_snaps/hyperion-model-knit/model-knit-1001.html index 2788efe3..7e6574d7 100644 --- a/tests/testthat/_snaps/hyperion-model-knit/model-knit-1001.html +++ b/tests/testthat/_snaps/hyperion-model-knit/model-knit-1001.html @@ -75,13 +75,13 @@ OMEGA(1,1) 0.1 No - OM1 CL :EXP + OM1 CL/F :EXP OMEGA(2,2) 0.1 No - OM2 VC :EXP + OM2 VC/F :EXP OMEGA(3,3) diff --git a/tests/testthat/_snaps/hyperion-model-print.md b/tests/testthat/_snaps/hyperion-model-print.md index dcd3109b..d8446642 100644 --- a/tests/testthat/_snaps/hyperion-model-print.md +++ b/tests/testthat/_snaps/hyperion-model-print.md @@ -31,11 +31,11 @@ Output - Parameter Initial Fixed Comment - ────────── ─────── ───── ─────────── - OMEGA(1,1) 0.1 No OM1 CL :EXP - OMEGA(2,2) 0.1 No OM2 VC :EXP - OMEGA(3,3) 0.1 No OM3 KA :EXP + Parameter Initial Fixed Comment + ────────── ─────── ───── ───────────── + OMEGA(1,1) 0.1 No OM1 CL/F :EXP + OMEGA(2,2) 0.1 No OM2 VC/F :EXP + OMEGA(3,3) 0.1 No OM3 KA :EXP Message -- Sigma Parameters -- diff --git a/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run001.html b/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run001.html index 22b95fa8..5f41d34f 100644 --- a/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run001.html +++ b/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run001.html @@ -62,7 +62,7 @@ OMEGA(1,1) - OM1 (TVCL) + OM1 NA NA LogNormal @@ -70,7 +70,7 @@ OMEGA(2,2) - OM2 (TVV) + OM2 NA NA LogNormal @@ -78,7 +78,7 @@ OMEGA(3,3) - OM3 (TVKA) + OM3 NA NA LogNormal diff --git a/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run002.html b/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run002.html index 22b95fa8..5f41d34f 100644 --- a/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run002.html +++ b/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run002.html @@ -62,7 +62,7 @@ OMEGA(1,1) - OM1 (TVCL) + OM1 NA NA LogNormal @@ -70,7 +70,7 @@ OMEGA(2,2) - OM2 (TVV) + OM2 NA NA LogNormal @@ -78,7 +78,7 @@ OMEGA(3,3) - OM3 (TVKA) + OM3 NA NA LogNormal diff --git a/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run003.html b/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run003.html index e00f60bc..1fd5dc47 100644 --- a/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run003.html +++ b/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run003.html @@ -62,7 +62,7 @@ OMEGA(1,1) - OM1 (TVCL) + OM1 NA NA LogNormal @@ -70,7 +70,7 @@ OMEGA(2,1) - OM1,2 (TVCL:TVV) + OM1,2 NA NA LogNormal @@ -78,7 +78,7 @@ OMEGA(2,2) - OM2 (TVV) + OM2 NA NA LogNormal @@ -86,7 +86,7 @@ OMEGA(3,3) - OM3 (TVKA) + OM3 NA NA LogNormal diff --git a/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run003b1.html b/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run003b1.html index 11eacafa..79b14e22 100644 --- a/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run003b1.html +++ b/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run003b1.html @@ -70,7 +70,7 @@ OMEGA(1,1) - OM1 (TVCL) + OM1 NA NA LogNormal @@ -78,7 +78,7 @@ OMEGA(2,1) - OM1,2 (TVCL:TVV) + OM1,2 NA NA LogNormal @@ -86,7 +86,7 @@ OMEGA(2,2) - OM2 (TVV) + OM2 NA NA LogNormal @@ -94,7 +94,7 @@ OMEGA(3,3) - OM3 (TVKA) + OM3 NA NA LogNormal diff --git a/tests/testthat/_snaps/hyperion-parameter-info-print.md b/tests/testthat/_snaps/hyperion-parameter-info-print.md index d003fe82..1fc4ede8 100644 --- a/tests/testthat/_snaps/hyperion-parameter-info-print.md +++ b/tests/testthat/_snaps/hyperion-parameter-info-print.md @@ -25,11 +25,11 @@ Output - parameter name display description parameterization associated_theta - ────────── ────────── ─────── ─────────── ──────────────── ──────────────── - OMEGA(1,1) OM1 (TVCL) NA NA LogNormal TVCL - OMEGA(2,2) OM2 (TVV) NA NA LogNormal TVV - OMEGA(3,3) OM3 (TVKA) NA NA LogNormal TVKA + parameter name display description parameterization associated_theta + ────────── ──── ─────── ─────────── ──────────────── ──────────────── + OMEGA(1,1) OM1 NA NA LogNormal TVCL + OMEGA(2,2) OM2 NA NA LogNormal TVV + OMEGA(3,3) OM3 NA NA LogNormal TVKA Message -- Sigma Parameters -- @@ -68,11 +68,11 @@ Output - parameter name display description parameterization associated_theta - ────────── ────────── ─────── ─────────── ──────────────── ──────────────── - OMEGA(1,1) OM1 (TVCL) NA NA LogNormal TVCL - OMEGA(2,2) OM2 (TVV) NA NA LogNormal TVV - OMEGA(3,3) OM3 (TVKA) NA NA LogNormal TVKA + parameter name display description parameterization associated_theta + ────────── ──── ─────── ─────────── ──────────────── ──────────────── + OMEGA(1,1) OM1 NA NA LogNormal TVCL + OMEGA(2,2) OM2 NA NA LogNormal TVV + OMEGA(3,3) OM3 NA NA LogNormal TVKA Message -- Sigma Parameters -- @@ -111,12 +111,12 @@ Output - parameter name display description parameterization associated_theta - ────────── ──────────────── ─────── ─────────── ──────────────── ──────────────── - OMEGA(1,1) OM1 (TVCL) NA NA LogNormal TVCL - OMEGA(2,1) OM1,2 (TVCL:TVV) NA NA LogNormal TVCL, TVV - OMEGA(2,2) OM2 (TVV) NA NA LogNormal TVV - OMEGA(3,3) OM3 (TVKA) NA NA LogNormal TVKA + parameter name display description parameterization associated_theta + ────────── ───── ─────── ─────────── ──────────────── ──────────────── + OMEGA(1,1) OM1 NA NA LogNormal TVCL + OMEGA(2,1) OM1,2 NA NA LogNormal TVCL, TVV + OMEGA(2,2) OM2 NA NA LogNormal TVV + OMEGA(3,3) OM3 NA NA LogNormal TVKA Message -- Sigma Parameters -- @@ -156,12 +156,12 @@ Output - parameter name display description parameterization associated_theta - ────────── ──────────────── ─────── ─────────── ──────────────── ──────────────── - OMEGA(1,1) OM1 (TVCL) NA NA LogNormal TVCL - OMEGA(2,1) OM1,2 (TVCL:TVV) NA NA LogNormal TVCL, TVV - OMEGA(2,2) OM2 (TVV) NA NA LogNormal TVV - OMEGA(3,3) OM3 (TVKA) NA NA LogNormal TVKA + parameter name display description parameterization associated_theta + ────────── ───── ─────── ─────────── ──────────────── ──────────────── + OMEGA(1,1) OM1 NA NA LogNormal TVCL + OMEGA(2,1) OM1,2 NA NA LogNormal TVCL, TVV + OMEGA(2,2) OM2 NA NA LogNormal TVV + OMEGA(3,3) OM3 NA NA LogNormal TVKA Message -- Sigma Parameters -- @@ -201,11 +201,11 @@ Output - parameter name display description parameterization associated_theta - ────────── ──────── ─────── ─────────── ──────────────── ──────────────── - OMEGA(1,1) OM1 (CL) NA NA LogNormal CL/F - OMEGA(2,2) OM2 (VC) NA NA LogNormal VC/F - OMEGA(3,3) OM3 (KA) NA NA LogNormal KA + parameter name display description parameterization associated_theta + ────────── ──── ─────── ─────────── ──────────────── ──────────────── + OMEGA(1,1) OM1 NA NA LogNormal CL/F + OMEGA(2,2) OM2 NA NA LogNormal VC/F + OMEGA(3,3) OM3 NA NA LogNormal KA Message -- Sigma Parameters -- diff --git a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run003.html b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run003.html index e559179f..928223ed 100644 --- a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run003.html +++ b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run003.html @@ -92,7 +92,7 @@ No - OM1,2 (TVCL:TVV) + OM1,2 (TVCL, TVV) ETA1:ETA2 0.07454 0.03134 diff --git a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run003b1.html b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run003b1.html index 56c7401a..25e03d09 100644 --- a/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run003b1.html +++ b/tests/testthat/_snaps/hyperion-summary-knit/summary-knit-run003b1.html @@ -84,7 +84,7 @@ No - OM1,2 (TVCL:TVV) + OM1,2 (TVCL, TVV) ETA1:ETA2 0.07218 NA diff --git a/tests/testthat/_snaps/hyperion-summary-print.md b/tests/testthat/_snaps/hyperion-summary-print.md index 04ec4c05..e8602a5c 100644 --- a/tests/testthat/_snaps/hyperion-summary-print.md +++ b/tests/testthat/_snaps/hyperion-summary-print.md @@ -160,12 +160,12 @@ Output - Parameter Random Effect Estimate SE RSE (%) Shrinkage (%) Fixed - ──────────────── ───────────── ──────── ─────── ─────── ───────────── ───── - OM1 (TVCL) ETA1 0.1223 0.05036 41.16 13.14 No - OM1,2 (TVCL:TVV) ETA1:ETA2 0.07454 0.03134 42.04 NA No - OM2 (TVV) ETA2 0.1239 0.03675 29.66 4.631 No - OM3 (TVKA) ETA3 0.1224 0.05628 45.97 24.34 No + Parameter Random Effect Estimate SE RSE (%) Shrinkage (%) Fixed + ───────────────── ───────────── ──────── ─────── ─────── ───────────── ───── + OM1 (TVCL) ETA1 0.1223 0.05036 41.16 13.14 No + OM1,2 (TVCL, TVV) ETA1:ETA2 0.07454 0.03134 42.04 NA No + OM2 (TVV) ETA2 0.1239 0.03675 29.66 4.631 No + OM3 (TVKA) ETA3 0.1224 0.05628 45.97 24.34 No Message -- Sigma Parameters -- @@ -221,12 +221,12 @@ Output - Parameter Random Effect Estimate Shrinkage (%) Fixed - ──────────────── ───────────── ──────── ───────────── ───── - OM1 (TVCL) ETA1 0.1233 13.66 No - OM1,2 (TVCL:TVV) ETA1:ETA2 0.07218 NA No - OM2 (TVV) ETA2 0.1246 4.625 No - OM3 (TVKA) ETA3 0.1239 24.36 No + Parameter Random Effect Estimate Shrinkage (%) Fixed + ───────────────── ───────────── ──────── ───────────── ───── + OM1 (TVCL) ETA1 0.1233 13.66 No + OM1,2 (TVCL, TVV) ETA1:ETA2 0.07218 NA No + OM2 (TVV) ETA2 0.1246 4.625 No + OM3 (TVKA) ETA3 0.1239 24.36 No Message -- Sigma Parameters -- diff --git a/tests/testthat/test-comments-query-edge.R b/tests/testthat/test-comments-query-edge.R index c3d61f11..09b89177 100644 --- a/tests/testthat/test-comments-query-edge.R +++ b/tests/testthat/test-comments-query-edge.R @@ -3,6 +3,16 @@ test_that("get_comment returns NULL for non-parameter names", { expect_null(get_comment(info, "OTHER1")) }) +test_that("resolve_comment strips name suffixes", { + theta1 <- ThetaComment(nonmem_name = "THETA1", name = "CL") + info <- ModelComments(theta = list(THETA1 = theta1)) + + expect_equal( + get_parameter_transform(info, "THETA1 (CL)"), + "Identity" + ) +}) + test_that("get_theta_names rejects non-ModelComments input", { expect_error( get_theta_names(list()), diff --git a/tests/testthat/test-comments-validation.R b/tests/testthat/test-comments-validation.R deleted file mode 100644 index bb0822d6..00000000 --- a/tests/testthat/test-comments-validation.R +++ /dev/null @@ -1,65 +0,0 @@ -test_that("omega associated_theta matches theta names case-insensitively", { - theta1 <- ThetaComment( - nonmem_name = "THETA1", - name = "CL/F" - ) - omega11 <- OmegaComment( - nonmem_name = "OMEGA(1,1)", - name = "IIV", - associated_theta = "cl/f" - ) - - info <- ModelComments( - theta = list(THETA1 = theta1), - omega = list(`OMEGA(1,1)` = omega11), - sigma = list() - ) - expect_equal(info@omega$`OMEGA(1,1)`@associated_theta, "CL/F") - - theta1 <- ThetaComment( - nonmem_name = "THETA1", - name = "cl/f" - ) - omega11 <- OmegaComment( - nonmem_name = "OMEGA(1,1)", - name = "IIV", - associated_theta = "CL/F" - ) - - info <- ModelComments( - theta = list(THETA1 = theta1), - omega = list(`OMEGA(1,1)` = omega11), - sigma = list() - ) - expect_equal(info@omega$`OMEGA(1,1)`@associated_theta, "cl/f") -}) - -test_that("omega associated_theta matches theta names by stripping suffix", { - theta1 <- ThetaComment( - nonmem_name = "THETA1", - name = "CL/F" - ) - theta2 <- ThetaComment( - nonmem_name = "THETA2", - name = "VC/F" - ) - omega11 <- OmegaComment( - nonmem_name = "OMEGA(1,1)", - name = "IIV-CL", - associated_theta = "CL" - ) - omega22 <- OmegaComment( - nonmem_name = "OMEGA(2,2)", - name = "IIV-VC", - associated_theta = "VC" - ) - - info <- ModelComments( - theta = list(THETA1 = theta1, THETA2 = theta2), - omega = list(`OMEGA(1,1)` = omega11, `OMEGA(2,2)` = omega22), - sigma = list() - ) - - expect_equal(info@omega$`OMEGA(1,1)`@associated_theta, "CL/F") - expect_equal(info@omega$`OMEGA(2,2)`@associated_theta, "VC/F") -}) diff --git a/tests/testthat/test-split-theta-reference.R b/tests/testthat/test-split-theta-reference.R deleted file mode 100644 index 71e84339..00000000 --- a/tests/testthat/test-split-theta-reference.R +++ /dev/null @@ -1,7 +0,0 @@ -test_that("split_theta_reference trims whitespace around separators", { - result <- split_theta_reference("CL / V") - expect_equal(result, c("CL", "V")) - - result <- split_theta_reference("CL, V") - expect_equal(result, c("CL", "V")) -}) diff --git a/tests/testthat/test-split_theta_reference.R b/tests/testthat/test-split_theta_reference.R deleted file mode 100644 index af10d6cc..00000000 --- a/tests/testthat/test-split_theta_reference.R +++ /dev/null @@ -1,15 +0,0 @@ -test_that("split_theta_reference respects known thetas", { - # No known thetas - splits - expect_equal(split_theta_reference("CL/F"), c("CL", "F")) - expect_equal(split_theta_reference("CL-V"), c("CL", "V")) - expect_equal(split_theta_reference("CL"), "CL") - - # Known theta - keeps as-is - expect_equal(split_theta_reference("CL/F", c("CL/F", "V")), "CL/F") - - # Case-insensitive match - expect_equal(split_theta_reference("cl/f", c("CL/F")), "cl/f") - - # No match in known - splits - expect_equal(split_theta_reference("CL/V", c("CL/F", "KA")), c("CL", "V")) -}) From b281545f80f0523ed52206abed675c47ce38843f Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Fri, 1 May 2026 08:47:18 -0400 Subject: [PATCH 25/41] cleanup --- R/comments-classes.R | 43 ++++++++++++++++++----- R/comments-lookup.R | 16 ++++++++- R/extendr-wrappers.R | 6 ++-- R/model-methods.R | 4 +-- R/zzz.R | 2 +- man/get_model_parameter_names.Rd | 2 +- man/map_parameterization.Rd | 2 +- src/rust/core/src/lib.rs | 4 +-- src/rust/nonmem/src/model/check.rs | 9 ++--- src/rust/nonmem/src/model/comment_info.rs | 22 ++++-------- src/rust/nonmem/src/model/parameters.rs | 2 +- 11 files changed, 68 insertions(+), 44 deletions(-) diff --git a/R/comments-classes.R b/R/comments-classes.R index 2fc8cfb7..5a3f12f5 100644 --- a/R/comments-classes.R +++ b/R/comments-classes.R @@ -366,20 +366,36 @@ find_parameter <- function(info, parameter, kind = NULL) { # 2. Match by @name property for (key in names(comments)) { if (identical(comments[[key]]@name, parameter)) { - matches <- c( - matches, - list(list(slot = slot, key = key, obj = comments[[key]])) - ) + if ( + !any(vapply( + matches, + function(m) m$slot == slot && m$key == key, + logical(1) + )) + ) { + matches <- c( + matches, + list(list(slot = slot, key = key, obj = comments[[key]])) + ) + } } } # 3. Match by @display property for (key in names(comments)) { if (identical(comments[[key]]@display, parameter)) { - matches <- c( - matches, - list(list(slot = slot, key = key, obj = comments[[key]])) - ) + if ( + !any(vapply( + matches, + function(m) m$slot == slot && m$key == key, + logical(1) + )) + ) { + matches <- c( + matches, + list(list(slot = slot, key = key, obj = comments[[key]])) + ) + } } } } @@ -449,7 +465,16 @@ update_param_info <- function( param_obj@description <- description } if (!is.null(parameterization)) { - param_obj@parameterization <- parameterization + mapped <- map_parameterization(parameterization) + if (is.na(mapped)) { + rlang::abort(paste0( + "Invalid parameterization: ", + parameterization, + ". Valid values: ", + paste(valid_parameterizations(), collapse = ", ") + )) + } + param_obj@parameterization <- mapped } # THETA/SIGMA: unit diff --git a/R/comments-lookup.R b/R/comments-lookup.R index 041fb930..f196588a 100644 --- a/R/comments-lookup.R +++ b/R/comments-lookup.R @@ -95,7 +95,11 @@ apply_lookup_defaults <- function(comment, lookup_path) { } } - if (is.null(comment@parameterization) && !is.null(entry$parameterization)) { + if ( + is.null(comment@parameterization) && + !is.null(entry$parameterization) && + !is.na(entry$parameterization) + ) { if (entry$parameterization != "none") { mapped <- map_parameterization(entry$parameterization) if (!is.na(mapped)) { @@ -183,6 +187,16 @@ add_parameter_to_lookup <- function( rlang::abort("name is required") } + # Treat NA the same as NULL (no value supplied) + if (!is.null(parameterization) && is.na(parameterization)) { + parameterization <- NULL + } + + # Treat "none" as "skip this field", symmetric with unit = "none" handling + if (!is.null(parameterization) && identical(parameterization, "none")) { + parameterization <- NULL + } + # Validate parameterization if provided if (!is.null(parameterization)) { mapped <- map_parameterization(parameterization) diff --git a/R/extendr-wrappers.R b/R/extendr-wrappers.R index a7c11c22..9cfe2053 100644 --- a/R/extendr-wrappers.R +++ b/R/extendr-wrappers.R @@ -18,7 +18,7 @@ NULL #' } init <- function(config_path) .Call(wrap__init, config_path) -set_panic_message <- function() .Call(wrap__set_panic_message) +silence_panic_output <- function() .Call(wrap__silence_panic_output) find_pharos_config_file <- function() .Call(wrap__find_pharos_config_file) @@ -167,7 +167,7 @@ get_parameters <- function(path, hide_off_diagonal_params = FALSE, only_method = #' #' @param model hyperion_nonmem_model object from read_model() #' -#' @return Named character vector with NONMEM names as names and user-friendly names as values +#' @return Named list with NONMEM names as names and user-friendly names as character values #' @keywords internal get_model_parameter_names <- function(model) .Call(wrap__get_model_parameter_names, model) @@ -183,7 +183,7 @@ get_model_comment_info <- function(model) .Call(wrap__get_model_comment_info, mo #' Canonicalize a parameterization alias to its PascalCase form. #' #' @param raw Parameterization alias (e.g. `"EXP"`, `"lognormal"`, `"PROP"`). -#' @return Canonical name (`"LogNormal"`, `"Proportional"`, ...) or `NULL` +#' @return Canonical name (`"LogNormal"`, `"Proportional"`, ...) or `NA_character_` #' if `raw` is not a recognized alias. #' @keywords internal map_parameterization <- function(raw) .Call(wrap__map_parameterization, raw) diff --git a/R/model-methods.R b/R/model-methods.R index e72ad735..17a8d652 100644 --- a/R/model-methods.R +++ b/R/model-methods.R @@ -123,8 +123,8 @@ build_running_summary <- function(object, n_iterations) { model_path <- from_config_relative(attr(object, "model_source")) # Derive expected output file paths to check existence before calling Rust. - # Rust panics print error messages via the panic hook before tryCatch can - # suppress them, so we must avoid calling Rust when files don't exist. + # Rust panics abort the R session; tryCatch cannot recover, so guard with + # file.exists() before calling Rust on missing files. base_name <- tools::file_path_sans_ext(basename(model_path)) output_dir <- file.path(dirname(model_path), base_name) diff --git a/R/zzz.R b/R/zzz.R index 591216cc..33102105 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -1,6 +1,6 @@ .onLoad <- function(libname, pkgname) { S7::methods_register() - set_panic_message() + silence_panic_output() # Set default hyperion options if not already set if (is.null(getOption("hyperion.significant_number_display"))) { diff --git a/man/get_model_parameter_names.Rd b/man/get_model_parameter_names.Rd index 4ef8c0d6..78e87355 100644 --- a/man/get_model_parameter_names.Rd +++ b/man/get_model_parameter_names.Rd @@ -10,7 +10,7 @@ get_model_parameter_names(model) \item{model}{hyperion_nonmem_model object from read_model()} } \value{ -Named character vector with NONMEM names as names and user-friendly names as values +Named list with NONMEM names as names and user-friendly names as character values } \description{ This function extracts parameter names using the pharos typed comment parser. diff --git a/man/map_parameterization.Rd b/man/map_parameterization.Rd index e0557448..b6814128 100644 --- a/man/map_parameterization.Rd +++ b/man/map_parameterization.Rd @@ -10,7 +10,7 @@ map_parameterization(raw) \item{raw}{Parameterization alias (e.g. \code{"EXP"}, \code{"lognormal"}, \code{"PROP"}).} } \value{ -Canonical name (\code{"LogNormal"}, \code{"Proportional"}, ...) or \code{NULL} +Canonical name (\code{"LogNormal"}, \code{"Proportional"}, ...) or \code{NA_character_} if \code{raw} is not a recognized alias. } \description{ diff --git a/src/rust/core/src/lib.rs b/src/rust/core/src/lib.rs index 1222b21b..c83933fa 100644 --- a/src/rust/core/src/lib.rs +++ b/src/rust/core/src/lib.rs @@ -39,7 +39,7 @@ pub fn find_config_dir() -> Result> { } #[extendr] -pub fn set_panic_message() { +pub fn silence_panic_output() { std::panic::set_hook(Box::new(|_| {})); } @@ -59,6 +59,6 @@ pub fn find_pharos_config_file() -> Result { extendr_module! { mod hyperion_core; - fn set_panic_message; + fn silence_panic_output; fn find_pharos_config_file; } diff --git a/src/rust/nonmem/src/model/check.rs b/src/rust/nonmem/src/model/check.rs index 8915e23e..bf5bd97c 100644 --- a/src/rust/nonmem/src/model/check.rs +++ b/src/rust/nonmem/src/model/check.rs @@ -8,7 +8,7 @@ use nonmem::{check_dataset as nonmem_check_dataset, check_model}; use crate::model::robj_to_model; -use crate::utils::{from_config_relative, load_nonmem_config, path_from_robj}; +use crate::utils::{load_nonmem_config, path_from_robj}; use hyperion_core::extendr_err; /// Checks mod file for nmtran errors @@ -56,12 +56,7 @@ pub fn check_model_wrap(model_path: Robj) -> Result { /// @export #[extendr] pub fn check_dataset(model: Robj) -> Result { - let source = model - .get_attrib("model_source") - .ok_or_extendr_err("Model object is missing model_source attribute")? - .as_str() - .ok_or_extendr_err("model_source attribute must be a character")?; - let model_path = from_config_relative(source)?; + let model_path = path_from_robj(&model, false)?; let model_dir = model_path .parent() .ok_or_extendr_err("Could not determine model directory")?; diff --git a/src/rust/nonmem/src/model/comment_info.rs b/src/rust/nonmem/src/model/comment_info.rs index 3340d963..f699cabb 100644 --- a/src/rust/nonmem/src/model/comment_info.rs +++ b/src/rust/nonmem/src/model/comment_info.rs @@ -42,11 +42,6 @@ pub struct ModelCommentInfo { pub sigmas: Vec<(String, SigmaCommentInfo)>, } -/// Canonicalize a Type1 raw parameterization string via pharos's `Transform::FromStr`. -fn map_parameterization_str(raw: &str) -> Option { - raw.parse::().ok().map(|t| t.to_string()) -} - fn build_theta_info(parsed: Option<&ParsedThetaComment>) -> ThetaCommentInfo { match parsed { Some(ParsedThetaComment::Type1(Type1Theta::WithUnit { @@ -56,9 +51,7 @@ fn build_theta_info(parsed: Option<&ParsedThetaComment>) -> ThetaCommentInfo { })) => ThetaCommentInfo { name: Some(parameter.clone()), unit: Some(unit.clone()), - parameterization: parametrization - .as_deref() - .and_then(map_parameterization_str), + parameterization: parametrization.as_deref().and_then(map_parameterization), }, Some(ParsedThetaComment::Type1(Type1Theta::Covariate { parameter })) => ThetaCommentInfo { name: Some(parameter.clone()), @@ -71,7 +64,7 @@ fn build_theta_info(parsed: Option<&ParsedThetaComment>) -> ThetaCommentInfo { })) => ThetaCommentInfo { name: Some(typ.clone()), unit: None, - parameterization: map_parameterization_str(parameterization), + parameterization: map_parameterization(parameterization), }, Some(ParsedThetaComment::Type2(t)) => ThetaCommentInfo { name: Some(t.name.clone()), @@ -99,7 +92,7 @@ fn build_omega_info(param: &OmegaSigmaParam) -> OmegaCommentInfo { Some(ParsedOmegaComment::Type1(o)) => OmegaCommentInfo { name: Some(o.name.clone()), associated_theta: vec![o.theta_name.clone()], - parameterization: map_parameterization_str(&o.parameterization), + parameterization: map_parameterization(&o.parameterization), }, Some(ParsedOmegaComment::Type2(o)) => OmegaCommentInfo { name: Some(o.name.clone()), @@ -127,10 +120,7 @@ fn build_sigma_info(param: &OmegaSigmaParam) -> SigmaCommentInfo { Some(ParsedSigmaComment::Type1(s)) => SigmaCommentInfo { name: Some(s.name.clone()), unit: None, - parameterization: s - .parameterization - .as_deref() - .and_then(map_parameterization_str), + parameterization: s.parameterization.as_deref().and_then(map_parameterization), }, Some(ParsedSigmaComment::Type2(s)) => SigmaCommentInfo { name: Some(s.name.clone()), @@ -199,12 +189,12 @@ pub fn get_model_comment_info(model: Robj) -> Result { /// Canonicalize a parameterization alias to its PascalCase form. /// /// @param raw Parameterization alias (e.g. `"EXP"`, `"lognormal"`, `"PROP"`). -/// @return Canonical name (`"LogNormal"`, `"Proportional"`, ...) or `NULL` +/// @return Canonical name (`"LogNormal"`, `"Proportional"`, ...) or `NA_character_` /// if `raw` is not a recognized alias. /// @keywords internal #[extendr] pub fn map_parameterization(raw: &str) -> Option { - map_parameterization_str(raw) + raw.parse::().ok().map(|t| t.to_string()) } extendr_module! { diff --git a/src/rust/nonmem/src/model/parameters.rs b/src/rust/nonmem/src/model/parameters.rs index 4646e85b..31cb58d4 100644 --- a/src/rust/nonmem/src/model/parameters.rs +++ b/src/rust/nonmem/src/model/parameters.rs @@ -230,7 +230,7 @@ pub fn get_parameters( /// /// @param model hyperion_nonmem_model object from read_model() /// -/// @return Named character vector with NONMEM names as names and user-friendly names as values +/// @return Named list with NONMEM names as names and user-friendly names as character values /// @keywords internal #[extendr] pub fn get_model_parameter_names(model: Robj) -> Result { From 8bc451f2ddd83a03b9c71a9bcef4fb8f49f4c1ca Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Wed, 6 May 2026 16:19:08 -0400 Subject: [PATCH 26/41] fix omega raw_names + names. clean up vignettes dir --- R/comments-classes.R | 5 ++ R/comments-fields.R | 1 + R/comments-query.R | 17 ++-- R/extendr-wrappers.R | 20 +++-- man/OmegaComment.Rd | 6 ++ man/copy_model.Rd | 1 + man/get_model_lineage.Rd | 19 ++-- src/rust/Cargo.lock | 14 +-- src/rust/Cargo.toml | 8 +- src/rust/nonmem/src/model/comment_info.rs | 12 ++- src/rust/nonmem/src/model/copy.rs | 2 + src/rust/nonmem/src/model/lineage.rs | 89 ++++++++++++++++--- .../audit-knit-run001.html | 4 + .../audit-knit-run002.html | 4 + .../audit-knit-run003.html | 5 ++ .../audit-knit-run003b1.html | 5 ++ tests/testthat/_snaps/hyperion-audit-print.md | 44 ++++----- .../parameter-info-knit-run001.html | 4 + .../parameter-info-knit-run002.html | 4 + .../parameter-info-knit-run003.html | 5 ++ .../parameter-info-knit-run003b1.html | 5 ++ .../_snaps/hyperion-parameter-info-print.md | 54 +++++------ tests/testthat/test-comments-query.R | 2 +- vignettes/ext.Rmd | 25 +++--- vignettes/grd.Rmd | 19 ++-- vignettes/hyperion_model.Rmd | 47 ++++++---- vignettes/lst.Rmd | 11 ++- vignettes/shk.Rmd | 15 ++-- 28 files changed, 296 insertions(+), 151 deletions(-) diff --git a/R/comments-classes.R b/R/comments-classes.R index 5a3f12f5..edc51410 100644 --- a/R/comments-classes.R +++ b/R/comments-classes.R @@ -100,6 +100,8 @@ ThetaComment <- S7::new_class( #' #' @param nonmem_name Character. The NONMEM parameter name (e.g., "OMEGA(1,1)"). #' @param name Character or NULL. User-defined parameter name (e.g., "IIV-CL"). +#' @param raw_name Character or NULL. Raw user label before pharos composition; +#' e.g. `"IIV"` when `name` is `"IIV (CL)"`. #' @param display Character or NULL. Display name for tables/output. #' @param description Character or NULL. Description of the parameter. #' @param parameterization Character or NULL. Transformation type. @@ -109,6 +111,8 @@ ThetaComment <- S7::new_class( #' \describe{ #' \item{nonmem_name}{The NONMEM parameter identifier.} #' \item{name}{User-friendly name parsed from comments.} +#' \item{raw_name}{Raw user label before pharos composition; e.g. `"IIV"` +#' when `name` is `"IIV (CL)"`.} #' \item{display}{Display name for tables. Falls back to `name` if NULL.} #' \item{description}{Longer description of what the parameter represents.} #' \item{parameterization}{Transformation type. Valid values: @@ -124,6 +128,7 @@ OmegaComment <- S7::new_class( parent = ParameterComment, properties = list( name = make_tracked_property("name"), + raw_name = make_tracked_property("raw_name"), display = make_tracked_property("display"), description = make_tracked_property("description"), parameterization = make_tracked_property( diff --git a/R/comments-fields.R b/R/comments-fields.R index ed374799..3d5d90e1 100644 --- a/R/comments-fields.R +++ b/R/comments-fields.R @@ -7,6 +7,7 @@ theta_fields <- function() { omega_fields <- function() { c( "name", + "raw_name", "display", "description", "parameterization", diff --git a/R/comments-query.R b/R/comments-query.R index d594aa3d..828ced1d 100644 --- a/R/comments-query.R +++ b/R/comments-query.R @@ -66,6 +66,13 @@ resolve_comment <- function(model_comments, nm, kind = NULL) { if (!is.null(cmt@name) && identical(cmt@name, lookup_nm)) { return(cmt) } + if ( + S7::S7_inherits(cmt, OmegaComment) && + !is.null(cmt@raw_name) && + identical(cmt@raw_name, lookup_nm) + ) { + return(cmt) + } } NULL } @@ -235,16 +242,8 @@ get_parameter_names <- function(x, lookup_path = NULL) { model_comments <- x extract_row <- function(comment) { - name_val <- comment@name %||% NA_character_ - if ( - is.na(name_val) && - S7::S7_inherits(comment, OmegaComment) && - length(comment@associated_theta %||% character()) > 0 - ) { - name_val <- paste(comment@associated_theta, collapse = ", ") - } data.frame( - name = name_val, + name = comment@name %||% NA_character_, display = comment@display %||% NA_character_, stringsAsFactors = FALSE ) diff --git a/R/extendr-wrappers.R b/R/extendr-wrappers.R index 9cfe2053..79c796f6 100644 --- a/R/extendr-wrappers.R +++ b/R/extendr-wrappers.R @@ -72,7 +72,7 @@ read_model_from_lst <- function(path) .Call(wrap__read_model_from_lst, path) #' @examples \dontrun{ #' copy_model(from = "model/nonmem/run001.mod", to = "model/nonmem/run002.mod") #' } -copy_model <- function(from, to, overwrite = FALSE, ext_file = NULL, update = 'none', jitter = NULL, jitter_excluded = NULL, seed = NULL, description, based_on = NULL, no_metadata = FALSE) .Call(wrap__copy_model_wrap, from, to, overwrite, ext_file, update, jitter, jitter_excluded, seed, description, based_on, no_metadata) +copy_model <- function(from, to, overwrite = FALSE, ext_file = NULL, update = 'none', jitter = NULL, jitter_excluded = NULL, seed = NULL, description, based_on = NULL, tags = NULL, no_metadata = FALSE) .Call(wrap__copy_model_wrap, from, to, overwrite, ext_file, update, jitter, jitter_excluded, seed, description, based_on, tags, no_metadata) #' Gets model run summary (internal implementation) #' @@ -124,18 +124,26 @@ check_dataset <- function(model) .Call(wrap__check_dataset, model) #' Get's model lineage #' -#' @param model_dir path to directory containing all models, or a hyperion_nonmem_model object -#' (uses the model's parent directory) +#' @param model_or_dir a hyperion_nonmem_model object (or a path to a `.mod`/`.ctl` +#' file), or a path to a directory. When a model is supplied the result is +#' filtered to that model and its ancestors only (chain leading up to the +#' model). When a directory is supplied no filter is applied. +#' @param scope `"project"` (default) walks the entire pharos project rooted at +#' `pharos.toml`; `"directory"` walks only the directory inferred from +#' `model_or_dir`. Node keys are always relative to the pharos config dir so +#' `based_on` references resolve consistently. #' #' @return hyperion_nonmem_tree S3 object #' @export #' #' @examples \dontrun{ -#' get_model_lineage("model/nonmem/") #' model <- read_model("model/nonmem/run001.mod") -#' get_model_lineage(model) +#' get_model_lineage(model) # ancestors of run001 +#' get_model_lineage(model, scope = "directory") # ancestors, walking only model's dir +#' get_model_lineage("model/nonmem/") # full project tree +#' get_model_lineage("model/nonmem/", scope = "directory") # only models under that dir #' } -get_model_lineage <- function(model_dir) .Call(wrap__get_model_lineage, model_dir) +get_model_lineage <- function(model_or_dir, scope = "project") .Call(wrap__get_model_lineage, model_or_dir, scope) #' Gets parameter estimates from model run #' diff --git a/man/OmegaComment.Rd b/man/OmegaComment.Rd index 8d7ca585..d9231543 100644 --- a/man/OmegaComment.Rd +++ b/man/OmegaComment.Rd @@ -7,6 +7,7 @@ OmegaComment( nonmem_name = character(0), name = NULL, + raw_name = NULL, display = NULL, description = NULL, parameterization = NULL, @@ -18,6 +19,9 @@ OmegaComment( \item{name}{Character or NULL. User-defined parameter name (e.g., "IIV-CL").} +\item{raw_name}{Character or NULL. Raw user label before pharos composition; +e.g. \code{"IIV"} when \code{name} is \code{"IIV (CL)"}.} + \item{display}{Character or NULL. Display name for tables/output.} \item{description}{Character or NULL. Description of the parameter.} @@ -34,6 +38,8 @@ Represents parsed comments for OMEGA parameters. \describe{ \item{nonmem_name}{The NONMEM parameter identifier.} \item{name}{User-friendly name parsed from comments.} +\item{raw_name}{Raw user label before pharos composition; e.g. \code{"IIV"} +when \code{name} is \code{"IIV (CL)"}.} \item{display}{Display name for tables. Falls back to \code{name} if NULL.} \item{description}{Longer description of what the parameter represents.} \item{parameterization}{Transformation type. Valid values: diff --git a/man/copy_model.Rd b/man/copy_model.Rd index 1a17d2a3..69769625 100644 --- a/man/copy_model.Rd +++ b/man/copy_model.Rd @@ -15,6 +15,7 @@ copy_model( seed = NULL, description, based_on = NULL, + tags = NULL, no_metadata = FALSE ) } diff --git a/man/get_model_lineage.Rd b/man/get_model_lineage.Rd index e1cc3048..e395c02d 100644 --- a/man/get_model_lineage.Rd +++ b/man/get_model_lineage.Rd @@ -4,11 +4,18 @@ \alias{get_model_lineage} \title{Get's model lineage} \usage{ -get_model_lineage(model_dir) +get_model_lineage(model_or_dir, scope = "project") } \arguments{ -\item{model_dir}{path to directory containing all models, or a hyperion_nonmem_model object -(uses the model's parent directory)} +\item{model_or_dir}{a hyperion_nonmem_model object (or a path to a \code{.mod}/\code{.ctl} +file), or a path to a directory. When a model is supplied the result is +filtered to that model and its ancestors only (chain leading up to the +model). When a directory is supplied no filter is applied.} + +\item{scope}{\code{"project"} (default) walks the entire pharos project rooted at +\code{pharos.toml}; \code{"directory"} walks only the directory inferred from +\code{model_or_dir}. Node keys are always relative to the pharos config dir so +\code{based_on} references resolve consistently.} } \value{ hyperion_nonmem_tree S3 object @@ -18,8 +25,10 @@ Get's model lineage } \examples{ \dontrun{ -get_model_lineage("model/nonmem/") model <- read_model("model/nonmem/run001.mod") -get_model_lineage(model) +get_model_lineage(model) # ancestors of run001 +get_model_lineage(model, scope = "directory") # ancestors, walking only model's dir +get_model_lineage("model/nonmem/") # full project tree +get_model_lineage("model/nonmem/", scope = "directory") # only models under that dir } } diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index b1c1b2cf..f8c53eae 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -141,7 +141,7 @@ dependencies = [ [[package]] name = "config" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=parsed-comment-pub#43faef861ea3ac7d42d6b7994a62d31d6e8802bc" +source = "git+https://github.com/a2-ai/pharos?branch=lineage-update#206f3105aa32cca4b6c76c417f146d5e2100a152" dependencies = [ "anyhow", "fs-err", @@ -741,7 +741,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "nonmem" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=parsed-comment-pub#43faef861ea3ac7d42d6b7994a62d31d6e8802bc" +source = "git+https://github.com/a2-ai/pharos?branch=lineage-update#206f3105aa32cca4b6c76c417f146d5e2100a152" dependencies = [ "anyhow", "blake3", @@ -766,7 +766,7 @@ dependencies = [ [[package]] name = "nonmem-parser" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=parsed-comment-pub#43faef861ea3ac7d42d6b7994a62d31d6e8802bc" +source = "git+https://github.com/a2-ai/pharos?branch=lineage-update#206f3105aa32cca4b6c76c417f146d5e2100a152" dependencies = [ "anyhow", "distrs", @@ -1125,7 +1125,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=parsed-comment-pub#43faef861ea3ac7d42d6b7994a62d31d6e8802bc" +source = "git+https://github.com/a2-ai/pharos?branch=lineage-update#206f3105aa32cca4b6c76c417f146d5e2100a152" dependencies = [ "anyhow", "config", @@ -1239,9 +1239,9 @@ checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "siphasher" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" [[package]] name = "slab" @@ -1377,7 +1377,7 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utils" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=parsed-comment-pub#43faef861ea3ac7d42d6b7994a62d31d6e8802bc" +source = "git+https://github.com/a2-ai/pharos?branch=lineage-update#206f3105aa32cca4b6c76c417f146d5e2100a152" dependencies = [ "anyhow", "fs-err", diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 2a463a3e..488eca8f 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -38,10 +38,10 @@ serde = { workspace = true } extendr-api = { version = "0.9.0", features = ["serde"] } # Pharos components -nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "parsed-comment-pub" } -nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", branch = "parsed-comment-pub" } -config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "parsed-comment-pub" } -scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "parsed-comment-pub" } +nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "lineage-update" } +nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", branch = "lineage-update" } +config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "lineage-update" } +scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "lineage-update" } # Core utilities anyhow = "1.0.100" diff --git a/src/rust/nonmem/src/model/comment_info.rs b/src/rust/nonmem/src/model/comment_info.rs index f699cabb..38c0ce2f 100644 --- a/src/rust/nonmem/src/model/comment_info.rs +++ b/src/rust/nonmem/src/model/comment_info.rs @@ -24,6 +24,7 @@ pub struct ThetaCommentInfo { #[derive(Debug, Clone, Serialize)] pub struct OmegaCommentInfo { pub name: Option, + pub raw_name: Option, pub associated_theta: Vec, pub parameterization: Option, } @@ -89,18 +90,21 @@ fn build_omega_info(param: &OmegaSigmaParam) -> OmegaCommentInfo { }; match inner { - Some(ParsedOmegaComment::Type1(o)) => OmegaCommentInfo { - name: Some(o.name.clone()), + Some(parsed @ ParsedOmegaComment::Type1(o)) => OmegaCommentInfo { + name: parsed.name(), + raw_name: Some(o.name.clone()), associated_theta: vec![o.theta_name.clone()], parameterization: map_parameterization(&o.parameterization), }, - Some(ParsedOmegaComment::Type2(o)) => OmegaCommentInfo { - name: Some(o.name.clone()), + Some(parsed @ ParsedOmegaComment::Type2(o)) => OmegaCommentInfo { + name: parsed.name(), + raw_name: Some(o.name.clone()), associated_theta: o.raw_theta_refs.clone(), parameterization: o.parameterization.as_ref().map(ToString::to_string), }, None => OmegaCommentInfo { name: None, + raw_name: None, associated_theta: Vec::new(), parameterization: None, }, diff --git a/src/rust/nonmem/src/model/copy.rs b/src/rust/nonmem/src/model/copy.rs index 67c90a1e..efc7eacd 100644 --- a/src/rust/nonmem/src/model/copy.rs +++ b/src/rust/nonmem/src/model/copy.rs @@ -115,6 +115,7 @@ pub fn copy_model_wrap( #[extendr(default = "NULL")] seed: Option, description: String, #[extendr(default = "NULL")] based_on: Option>, + #[extendr(default = "NULL")] tags: Option>, #[extendr(default = "FALSE")] no_metadata: bool, ) -> Result<()> { // Parse input parameters @@ -133,6 +134,7 @@ pub fn copy_model_wrap( jitter_excluded: jitter_excluded_parsed, description, based_on: based_on.unwrap_or_default(), + tags: tags.unwrap_or_default(), no_metadata, }; diff --git a/src/rust/nonmem/src/model/lineage.rs b/src/rust/nonmem/src/model/lineage.rs index f4b46da9..38c66751 100644 --- a/src/rust/nonmem/src/model/lineage.rs +++ b/src/rust/nonmem/src/model/lineage.rs @@ -8,7 +8,7 @@ use std::collections::{HashMap, HashSet}; use nonmem::{LineageTree, ModelMetadata, OutputFileHash, RunEndFile, RunStartFile}; use crate::utils::{path_from_robj, to_config_relative}; -use hyperion_core::{OptionExt, ResultExt}; +use hyperion_core::{OptionExt, ResultExt, extendr_err, find_config_dir}; /// R-compatible version of RunEndFile with u128 -> f64 conversion #[derive(Debug, Serialize, Deserialize, Clone)] @@ -71,33 +71,100 @@ impl From for RLineageTree { /// Get's model lineage /// -/// @param model_dir path to directory containing all models, or a hyperion_nonmem_model object -/// (uses the model's parent directory) +/// @param model_or_dir a hyperion_nonmem_model object (or a path to a `.mod`/`.ctl` +/// file), or a path to a directory. When a model is supplied the result is +/// filtered to that model and its ancestors only (chain leading up to the +/// model). When a directory is supplied no filter is applied. +/// @param scope `"project"` (default) walks the entire pharos project rooted at +/// `pharos.toml`; `"directory"` walks only the directory inferred from +/// `model_or_dir`. Node keys are always relative to the pharos config dir so +/// `based_on` references resolve consistently. /// /// @return hyperion_nonmem_tree S3 object /// @export /// /// @examples \dontrun{ -/// get_model_lineage("model/nonmem/") /// model <- read_model("model/nonmem/run001.mod") -/// get_model_lineage(model) +/// get_model_lineage(model) # ancestors of run001 +/// get_model_lineage(model, scope = "directory") # ancestors, walking only model's dir +/// get_model_lineage("model/nonmem/") # full project tree +/// get_model_lineage("model/nonmem/", scope = "directory") # only models under that dir /// } #[extendr] -pub fn get_model_lineage(model_dir: Robj) -> Result { - let path = path_from_robj(&model_dir, false)?; +pub fn get_model_lineage( + model_or_dir: Robj, + #[extendr(default = "\"project\"")] scope: &str, +) -> Result { + let path = path_from_robj(&model_or_dir, false)?; + let user_passed_model = path.is_file(); // If it's a file, use parent directory; if directory, use as-is - let model_dir = if path.is_file() { + let model_dir = if user_passed_model { path.parent() .ok_or_extendr_err("Could not determine model directory")? .to_path_buf() } else { - path + path.clone() }; - // Create lineage tree from folder - let lineage = LineageTree::from_folder(&model_dir) + // Use the pharos config dir as project_root so node keys are + // config-relative, matching how `based_on` is stored. Without this, a + // model under e.g. `struct/` walked in isolation would key as `1001.mod` + // while based_on says `struct/1001.mod` and parent links never resolve. + // Falls back to model_dir for projects without a pharos.toml. + let project_root = find_config_dir()?.unwrap_or_else(|| model_dir.clone()); + + let start = match scope { + "project" => project_root.clone(), + "directory" => model_dir.clone(), + other => { + return Err(extendr_err!( + "Invalid scope '{}': must be 'project' or 'directory'", + other + )); + } + }; + + let lineage = LineageTree::build(&start, &project_root, true) .map_to_extendr_err("Pharos failed to create lineage tree")?; + // When the user passes a model file, filter the tree to just that model + // and its ancestors (the chain leading up to it). Mirrors `pharos nonmem + // lineage --to `. + let lineage = if user_passed_model { + let abs_path = + std::fs::canonicalize(&path).map_to_extendr_err("Failed to canonicalize model path")?; + let abs_root = std::fs::canonicalize(&project_root) + .map_to_extendr_err("Failed to canonicalize project root")?; + let model_key = abs_path + .strip_prefix(&abs_root) + .map_err(|_| { + extendr_err!( + "Model '{}' is outside the project root '{}'", + abs_path.display(), + abs_root.display() + ) + })? + .to_string_lossy() + .to_string(); + + let chain = lineage.get_tree_up_to(&model_key); + let chain_keys: HashSet = chain.iter().map(|(k, _)| k.clone()).collect(); + LineageTree { + nodes: lineage + .nodes + .into_iter() + .filter(|(k, _)| chain_keys.contains(k)) + .collect(), + metadata: lineage + .metadata + .into_iter() + .filter(|(k, _)| chain_keys.contains(k)) + .collect(), + } + } else { + lineage + }; + // Convert to R-compatible version (u128 -> f64) and attach source directory (relative to pharos.toml) let source_dir = to_config_relative(&model_dir)?; let r_lineage: RLineageTree = RLineageTree::from(lineage).with_source_dir(source_dir); diff --git a/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run001.html b/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run001.html index 3367138e..a4f91bf0 100644 --- a/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run001.html +++ b/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run001.html @@ -53,6 +53,7 @@ parameter name + raw_name display description parameterization @@ -63,6 +64,7 @@ OMEGA(1,1) extdata/models/onecmt/run001/run001.lst + extdata/models/onecmt/run001/run001.lst default default extdata/models/onecmt/run001/run001.lst @@ -71,6 +73,7 @@ OMEGA(2,2) extdata/models/onecmt/run001/run001.lst + extdata/models/onecmt/run001/run001.lst default default extdata/models/onecmt/run001/run001.lst @@ -79,6 +82,7 @@ OMEGA(3,3) extdata/models/onecmt/run001/run001.lst + extdata/models/onecmt/run001/run001.lst default default extdata/models/onecmt/run001/run001.lst diff --git a/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run002.html b/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run002.html index f8a4c4ec..65b91765 100644 --- a/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run002.html +++ b/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run002.html @@ -53,6 +53,7 @@ parameter name + raw_name display description parameterization @@ -63,6 +64,7 @@ OMEGA(1,1) extdata/models/onecmt/run002/run002.lst + extdata/models/onecmt/run002/run002.lst default default extdata/models/onecmt/run002/run002.lst @@ -71,6 +73,7 @@ OMEGA(2,2) extdata/models/onecmt/run002/run002.lst + extdata/models/onecmt/run002/run002.lst default default extdata/models/onecmt/run002/run002.lst @@ -79,6 +82,7 @@ OMEGA(3,3) extdata/models/onecmt/run002/run002.lst + extdata/models/onecmt/run002/run002.lst default default extdata/models/onecmt/run002/run002.lst diff --git a/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run003.html b/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run003.html index b31b3c81..5e483292 100644 --- a/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run003.html +++ b/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run003.html @@ -53,6 +53,7 @@ parameter name + raw_name display description parameterization @@ -63,6 +64,7 @@ OMEGA(1,1) extdata/models/onecmt/run003/run003.lst + extdata/models/onecmt/run003/run003.lst default default extdata/models/onecmt/run003/run003.lst @@ -71,6 +73,7 @@ OMEGA(2,1) extdata/models/onecmt/run003/run003.lst + extdata/models/onecmt/run003/run003.lst default default extdata/models/onecmt/run003/run003.lst @@ -79,6 +82,7 @@ OMEGA(2,2) extdata/models/onecmt/run003/run003.lst + extdata/models/onecmt/run003/run003.lst default default extdata/models/onecmt/run003/run003.lst @@ -87,6 +91,7 @@ OMEGA(3,3) extdata/models/onecmt/run003/run003.lst + extdata/models/onecmt/run003/run003.lst default default extdata/models/onecmt/run003/run003.lst diff --git a/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run003b1.html b/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run003b1.html index 0bd67ea1..5a7add14 100644 --- a/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run003b1.html +++ b/tests/testthat/_snaps/hyperion-audit-knit/audit-knit-run003b1.html @@ -61,6 +61,7 @@ parameter name + raw_name display description parameterization @@ -71,6 +72,7 @@ OMEGA(1,1) extdata/models/onecmt/run003b1/run003b1.lst + extdata/models/onecmt/run003b1/run003b1.lst default default extdata/models/onecmt/run003b1/run003b1.lst @@ -79,6 +81,7 @@ OMEGA(2,1) extdata/models/onecmt/run003b1/run003b1.lst + extdata/models/onecmt/run003b1/run003b1.lst default default extdata/models/onecmt/run003b1/run003b1.lst @@ -87,6 +90,7 @@ OMEGA(2,2) extdata/models/onecmt/run003b1/run003b1.lst + extdata/models/onecmt/run003b1/run003b1.lst default default extdata/models/onecmt/run003b1/run003b1.lst @@ -95,6 +99,7 @@ OMEGA(3,3) extdata/models/onecmt/run003b1/run003b1.lst + extdata/models/onecmt/run003b1/run003b1.lst default default extdata/models/onecmt/run003b1/run003b1.lst diff --git a/tests/testthat/_snaps/hyperion-audit-print.md b/tests/testthat/_snaps/hyperion-audit-print.md index 830449e4..e0fd4e9a 100644 --- a/tests/testthat/_snaps/hyperion-audit-print.md +++ b/tests/testthat/_snaps/hyperion-audit-print.md @@ -25,11 +25,11 @@ Output - parameter name display description parameterization associated_theta - ────────── ─────────────────────────────────────── ─────── ─────────── ─────────────────────────────────────── ─────────────────────────────────────── - OMEGA(1,1) extdata/models/onecmt/run001/run001.lst default default extdata/models/onecmt/run001/run001.lst extdata/models/onecmt/run001/run001.lst - OMEGA(2,2) extdata/models/onecmt/run001/run001.lst default default extdata/models/onecmt/run001/run001.lst extdata/models/onecmt/run001/run001.lst - OMEGA(3,3) extdata/models/onecmt/run001/run001.lst default default extdata/models/onecmt/run001/run001.lst extdata/models/onecmt/run001/run001.lst + parameter name raw_name display description parameterization associated_theta + ────────── ─────────────────────────────────────── ─────────────────────────────────────── ─────── ─────────── ─────────────────────────────────────── ─────────────────────────────────────── + OMEGA(1,1) extdata/models/onecmt/run001/run001.lst extdata/models/onecmt/run001/run001.lst default default extdata/models/onecmt/run001/run001.lst extdata/models/onecmt/run001/run001.lst + OMEGA(2,2) extdata/models/onecmt/run001/run001.lst extdata/models/onecmt/run001/run001.lst default default extdata/models/onecmt/run001/run001.lst extdata/models/onecmt/run001/run001.lst + OMEGA(3,3) extdata/models/onecmt/run001/run001.lst extdata/models/onecmt/run001/run001.lst default default extdata/models/onecmt/run001/run001.lst extdata/models/onecmt/run001/run001.lst Message -- Sigma Sources -- @@ -68,11 +68,11 @@ Output - parameter name display description parameterization associated_theta - ────────── ─────────────────────────────────────── ─────── ─────────── ─────────────────────────────────────── ─────────────────────────────────────── - OMEGA(1,1) extdata/models/onecmt/run002/run002.lst default default extdata/models/onecmt/run002/run002.lst extdata/models/onecmt/run002/run002.lst - OMEGA(2,2) extdata/models/onecmt/run002/run002.lst default default extdata/models/onecmt/run002/run002.lst extdata/models/onecmt/run002/run002.lst - OMEGA(3,3) extdata/models/onecmt/run002/run002.lst default default extdata/models/onecmt/run002/run002.lst extdata/models/onecmt/run002/run002.lst + parameter name raw_name display description parameterization associated_theta + ────────── ─────────────────────────────────────── ─────────────────────────────────────── ─────── ─────────── ─────────────────────────────────────── ─────────────────────────────────────── + OMEGA(1,1) extdata/models/onecmt/run002/run002.lst extdata/models/onecmt/run002/run002.lst default default extdata/models/onecmt/run002/run002.lst extdata/models/onecmt/run002/run002.lst + OMEGA(2,2) extdata/models/onecmt/run002/run002.lst extdata/models/onecmt/run002/run002.lst default default extdata/models/onecmt/run002/run002.lst extdata/models/onecmt/run002/run002.lst + OMEGA(3,3) extdata/models/onecmt/run002/run002.lst extdata/models/onecmt/run002/run002.lst default default extdata/models/onecmt/run002/run002.lst extdata/models/onecmt/run002/run002.lst Message -- Sigma Sources -- @@ -111,12 +111,12 @@ Output - parameter name display description parameterization associated_theta - ────────── ─────────────────────────────────────── ─────── ─────────── ─────────────────────────────────────── ─────────────────────────────────────── - OMEGA(1,1) extdata/models/onecmt/run003/run003.lst default default extdata/models/onecmt/run003/run003.lst extdata/models/onecmt/run003/run003.lst - OMEGA(2,1) extdata/models/onecmt/run003/run003.lst default default extdata/models/onecmt/run003/run003.lst extdata/models/onecmt/run003/run003.lst - OMEGA(2,2) extdata/models/onecmt/run003/run003.lst default default extdata/models/onecmt/run003/run003.lst extdata/models/onecmt/run003/run003.lst - OMEGA(3,3) extdata/models/onecmt/run003/run003.lst default default extdata/models/onecmt/run003/run003.lst extdata/models/onecmt/run003/run003.lst + parameter name raw_name display description parameterization associated_theta + ────────── ─────────────────────────────────────── ─────────────────────────────────────── ─────── ─────────── ─────────────────────────────────────── ─────────────────────────────────────── + OMEGA(1,1) extdata/models/onecmt/run003/run003.lst extdata/models/onecmt/run003/run003.lst default default extdata/models/onecmt/run003/run003.lst extdata/models/onecmt/run003/run003.lst + OMEGA(2,1) extdata/models/onecmt/run003/run003.lst extdata/models/onecmt/run003/run003.lst default default extdata/models/onecmt/run003/run003.lst extdata/models/onecmt/run003/run003.lst + OMEGA(2,2) extdata/models/onecmt/run003/run003.lst extdata/models/onecmt/run003/run003.lst default default extdata/models/onecmt/run003/run003.lst extdata/models/onecmt/run003/run003.lst + OMEGA(3,3) extdata/models/onecmt/run003/run003.lst extdata/models/onecmt/run003/run003.lst default default extdata/models/onecmt/run003/run003.lst extdata/models/onecmt/run003/run003.lst Message -- Sigma Sources -- @@ -156,12 +156,12 @@ Output - parameter name display description parameterization associated_theta - ────────── ─────────────────────────────────────────── ─────── ─────────── ─────────────────────────────────────────── ─────────────────────────────────────────── - OMEGA(1,1) extdata/models/onecmt/run003b1/run003b1.lst default default extdata/models/onecmt/run003b1/run003b1.lst extdata/models/onecmt/run003b1/run003b1.lst - OMEGA(2,1) extdata/models/onecmt/run003b1/run003b1.lst default default extdata/models/onecmt/run003b1/run003b1.lst extdata/models/onecmt/run003b1/run003b1.lst - OMEGA(2,2) extdata/models/onecmt/run003b1/run003b1.lst default default extdata/models/onecmt/run003b1/run003b1.lst extdata/models/onecmt/run003b1/run003b1.lst - OMEGA(3,3) extdata/models/onecmt/run003b1/run003b1.lst default default extdata/models/onecmt/run003b1/run003b1.lst extdata/models/onecmt/run003b1/run003b1.lst + parameter name raw_name display description parameterization associated_theta + ────────── ─────────────────────────────────────────── ─────────────────────────────────────────── ─────── ─────────── ─────────────────────────────────────────── ─────────────────────────────────────────── + OMEGA(1,1) extdata/models/onecmt/run003b1/run003b1.lst extdata/models/onecmt/run003b1/run003b1.lst default default extdata/models/onecmt/run003b1/run003b1.lst extdata/models/onecmt/run003b1/run003b1.lst + OMEGA(2,1) extdata/models/onecmt/run003b1/run003b1.lst extdata/models/onecmt/run003b1/run003b1.lst default default extdata/models/onecmt/run003b1/run003b1.lst extdata/models/onecmt/run003b1/run003b1.lst + OMEGA(2,2) extdata/models/onecmt/run003b1/run003b1.lst extdata/models/onecmt/run003b1/run003b1.lst default default extdata/models/onecmt/run003b1/run003b1.lst extdata/models/onecmt/run003b1/run003b1.lst + OMEGA(3,3) extdata/models/onecmt/run003b1/run003b1.lst extdata/models/onecmt/run003b1/run003b1.lst default default extdata/models/onecmt/run003b1/run003b1.lst extdata/models/onecmt/run003b1/run003b1.lst Message -- Sigma Sources -- diff --git a/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run001.html b/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run001.html index 5f41d34f..8824b7da 100644 --- a/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run001.html +++ b/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run001.html @@ -53,6 +53,7 @@ parameter name + raw_name display description parameterization @@ -62,6 +63,7 @@ OMEGA(1,1) + OM1 (TVCL) OM1 NA NA @@ -70,6 +72,7 @@ OMEGA(2,2) + OM2 (TVV) OM2 NA NA @@ -78,6 +81,7 @@ OMEGA(3,3) + OM3 (TVKA) OM3 NA NA diff --git a/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run002.html b/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run002.html index 5f41d34f..8824b7da 100644 --- a/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run002.html +++ b/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run002.html @@ -53,6 +53,7 @@ parameter name + raw_name display description parameterization @@ -62,6 +63,7 @@ OMEGA(1,1) + OM1 (TVCL) OM1 NA NA @@ -70,6 +72,7 @@ OMEGA(2,2) + OM2 (TVV) OM2 NA NA @@ -78,6 +81,7 @@ OMEGA(3,3) + OM3 (TVKA) OM3 NA NA diff --git a/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run003.html b/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run003.html index 1fd5dc47..7afede63 100644 --- a/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run003.html +++ b/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run003.html @@ -53,6 +53,7 @@ parameter name + raw_name display description parameterization @@ -62,6 +63,7 @@ OMEGA(1,1) + OM1 (TVCL) OM1 NA NA @@ -70,6 +72,7 @@ OMEGA(2,1) + OM1,2 (TVCL, TVV) OM1,2 NA NA @@ -78,6 +81,7 @@ OMEGA(2,2) + OM2 (TVV) OM2 NA NA @@ -86,6 +90,7 @@ OMEGA(3,3) + OM3 (TVKA) OM3 NA NA diff --git a/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run003b1.html b/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run003b1.html index 79b14e22..2be3a872 100644 --- a/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run003b1.html +++ b/tests/testthat/_snaps/hyperion-parameter-info-knit/parameter-info-knit-run003b1.html @@ -61,6 +61,7 @@ parameter name + raw_name display description parameterization @@ -70,6 +71,7 @@ OMEGA(1,1) + OM1 (TVCL) OM1 NA NA @@ -78,6 +80,7 @@ OMEGA(2,1) + OM1,2 (TVCL, TVV) OM1,2 NA NA @@ -86,6 +89,7 @@ OMEGA(2,2) + OM2 (TVV) OM2 NA NA @@ -94,6 +98,7 @@ OMEGA(3,3) + OM3 (TVKA) OM3 NA NA diff --git a/tests/testthat/_snaps/hyperion-parameter-info-print.md b/tests/testthat/_snaps/hyperion-parameter-info-print.md index 1fc4ede8..30a78db8 100644 --- a/tests/testthat/_snaps/hyperion-parameter-info-print.md +++ b/tests/testthat/_snaps/hyperion-parameter-info-print.md @@ -25,11 +25,11 @@ Output - parameter name display description parameterization associated_theta - ────────── ──── ─────── ─────────── ──────────────── ──────────────── - OMEGA(1,1) OM1 NA NA LogNormal TVCL - OMEGA(2,2) OM2 NA NA LogNormal TVV - OMEGA(3,3) OM3 NA NA LogNormal TVKA + parameter name raw_name display description parameterization associated_theta + ────────── ────────── ──────── ─────── ─────────── ──────────────── ──────────────── + OMEGA(1,1) OM1 (TVCL) OM1 NA NA LogNormal TVCL + OMEGA(2,2) OM2 (TVV) OM2 NA NA LogNormal TVV + OMEGA(3,3) OM3 (TVKA) OM3 NA NA LogNormal TVKA Message -- Sigma Parameters -- @@ -68,11 +68,11 @@ Output - parameter name display description parameterization associated_theta - ────────── ──── ─────── ─────────── ──────────────── ──────────────── - OMEGA(1,1) OM1 NA NA LogNormal TVCL - OMEGA(2,2) OM2 NA NA LogNormal TVV - OMEGA(3,3) OM3 NA NA LogNormal TVKA + parameter name raw_name display description parameterization associated_theta + ────────── ────────── ──────── ─────── ─────────── ──────────────── ──────────────── + OMEGA(1,1) OM1 (TVCL) OM1 NA NA LogNormal TVCL + OMEGA(2,2) OM2 (TVV) OM2 NA NA LogNormal TVV + OMEGA(3,3) OM3 (TVKA) OM3 NA NA LogNormal TVKA Message -- Sigma Parameters -- @@ -111,12 +111,12 @@ Output - parameter name display description parameterization associated_theta - ────────── ───── ─────── ─────────── ──────────────── ──────────────── - OMEGA(1,1) OM1 NA NA LogNormal TVCL - OMEGA(2,1) OM1,2 NA NA LogNormal TVCL, TVV - OMEGA(2,2) OM2 NA NA LogNormal TVV - OMEGA(3,3) OM3 NA NA LogNormal TVKA + parameter name raw_name display description parameterization associated_theta + ────────── ───────────────── ──────── ─────── ─────────── ──────────────── ──────────────── + OMEGA(1,1) OM1 (TVCL) OM1 NA NA LogNormal TVCL + OMEGA(2,1) OM1,2 (TVCL, TVV) OM1,2 NA NA LogNormal TVCL, TVV + OMEGA(2,2) OM2 (TVV) OM2 NA NA LogNormal TVV + OMEGA(3,3) OM3 (TVKA) OM3 NA NA LogNormal TVKA Message -- Sigma Parameters -- @@ -156,12 +156,12 @@ Output - parameter name display description parameterization associated_theta - ────────── ───── ─────── ─────────── ──────────────── ──────────────── - OMEGA(1,1) OM1 NA NA LogNormal TVCL - OMEGA(2,1) OM1,2 NA NA LogNormal TVCL, TVV - OMEGA(2,2) OM2 NA NA LogNormal TVV - OMEGA(3,3) OM3 NA NA LogNormal TVKA + parameter name raw_name display description parameterization associated_theta + ────────── ───────────────── ──────── ─────── ─────────── ──────────────── ──────────────── + OMEGA(1,1) OM1 (TVCL) OM1 NA NA LogNormal TVCL + OMEGA(2,1) OM1,2 (TVCL, TVV) OM1,2 NA NA LogNormal TVCL, TVV + OMEGA(2,2) OM2 (TVV) OM2 NA NA LogNormal TVV + OMEGA(3,3) OM3 (TVKA) OM3 NA NA LogNormal TVKA Message -- Sigma Parameters -- @@ -201,11 +201,11 @@ Output - parameter name display description parameterization associated_theta - ────────── ──── ─────── ─────────── ──────────────── ──────────────── - OMEGA(1,1) OM1 NA NA LogNormal CL/F - OMEGA(2,2) OM2 NA NA LogNormal VC/F - OMEGA(3,3) OM3 NA NA LogNormal KA + parameter name raw_name display description parameterization associated_theta + ────────── ────────── ──────── ─────── ─────────── ──────────────── ──────────────── + OMEGA(1,1) OM1 (CL/F) OM1 NA NA LogNormal CL/F + OMEGA(2,2) OM2 (VC/F) OM2 NA NA LogNormal VC/F + OMEGA(3,3) OM3 (KA) OM3 NA NA LogNormal KA Message -- Sigma Parameters -- diff --git a/tests/testthat/test-comments-query.R b/tests/testthat/test-comments-query.R index 8974a6b3..30ab244b 100644 --- a/tests/testthat/test-comments-query.R +++ b/tests/testthat/test-comments-query.R @@ -34,7 +34,7 @@ test_that("get_parameter_names uses associated_theta when name is NA", { ) omega11 <- OmegaComment( nonmem_name = "OMEGA(1,1)", - name = NA_character_, + name = "CL/F", associated_theta = "CL/F" ) diff --git a/vignettes/ext.Rmd b/vignettes/ext.Rmd index e67f0e99..1f55a81c 100644 --- a/vignettes/ext.Rmd +++ b/vignettes/ext.Rmd @@ -12,6 +12,7 @@ knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) +knitr::opts_knit$set(root.dir = system.file("extdata", package = "hyperion")) ``` ```{r setup} @@ -19,24 +20,22 @@ library(ggplot2) library(dplyr) library(tidyr) library(hyperion) - -test_data_dir <- system.file("extdata", package = "hyperion") ``` ## get parameter estimates ```{r} -get_parameters(file.path(test_data_dir, "models", "onecmt", "run002")) +get_parameters(file.path("models", "onecmt", "run002")) -get_parameters(file.path(test_data_dir, "models", "onecmt", "run002.mod")) +get_parameters(file.path("models", "onecmt", "run002.mod")) -get_parameters(file.path(test_data_dir, "models", "onecmt", "run002", "run002.ext")) +get_parameters(file.path("models", "onecmt", "run002", "run002.ext")) -get_parameters(file.path(test_data_dir, "models", "onecmt", "run002_metadata.json")) +get_parameters(file.path("models", "onecmt", "run002_metadata.json")) ``` ```{r} get_parameters( - file.path(test_data_dir, "models", "onecmt", "run002") + file.path("models", "onecmt", "run002") ) |> mutate( `95% CI` = paste0( @@ -50,7 +49,7 @@ get_parameters( ```{r} untransformed_df <- get_parameters( - file.path(test_data_dir, "models", "onecmt", "run002") + file.path("models", "onecmt", "run002") ) transformed_df <- untransformed_df |> @@ -76,7 +75,7 @@ transformed_df ## Read ext file ```{r} read_ext_file( - file.path(test_data_dir, "ext", "bql.ext"), + file.path("ext", "bql.ext"), line_prefixes = "-1000000000", parameters_only = TRUE ) @@ -84,7 +83,7 @@ read_ext_file( ```{r} -read_ext_file(file.path(test_data_dir, "ext", "itsimp.ext"), only_method = "its") |> +read_ext_file(file.path("ext", "itsimp.ext"), only_method = "its") |> filter(iteration > 0) |> pivot_longer( cols = starts_with("THETA"), @@ -103,7 +102,7 @@ read_ext_file(file.path(test_data_dir, "ext", "itsimp.ext"), only_method = "its" ``` ```{r} -read_ext_file(file.path(test_data_dir, "ext", "itsimp.ext"), only_method = "its") |> +read_ext_file(file.path("ext", "itsimp.ext"), only_method = "its") |> filter(iteration > 0) |> pivot_longer( cols = starts_with("OMEGA"), @@ -122,7 +121,7 @@ read_ext_file(file.path(test_data_dir, "ext", "itsimp.ext"), only_method = "its" ``` ```{r} -read_ext_file(file.path(test_data_dir, "ext", "itsimp.ext"), only_method = "its") |> +read_ext_file(file.path("ext", "itsimp.ext"), only_method = "its") |> filter(iteration > 0) |> pivot_longer( cols = starts_with("SIGMA"), @@ -142,7 +141,7 @@ read_ext_file(file.path(test_data_dir, "ext", "itsimp.ext"), only_method = "its" ```{r} -read_ext_file(file.path(test_data_dir, "ext", "example6.txt.ext")) |> +read_ext_file(file.path("ext", "example6.txt.ext")) |> filter(iteration == -1000000000) ``` diff --git a/vignettes/grd.Rmd b/vignettes/grd.Rmd index ddb863cc..b1e51814 100644 --- a/vignettes/grd.Rmd +++ b/vignettes/grd.Rmd @@ -12,17 +12,16 @@ knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) +knitr::opts_knit$set(root.dir = system.file("extdata", package = "hyperion")) ``` ```{r setup} library(hyperion) library(dplyr) - -test_data_dir <- system.file("extdata", package = "hyperion") ``` ```{r} -get_gradients(file.path(test_data_dir, "grd", "bql.grd")) |> +get_gradients(file.path("grd", "bql.grd")) |> slice_tail(n = 1) |> summarize(any_zero = any(across(everything(), ~ .x == 0))) |> pull(any_zero) |> @@ -30,20 +29,20 @@ get_gradients(file.path(test_data_dir, "grd", "bql.grd")) |> ``` ```{r} -get_gradients(file.path(test_data_dir, "models", "onecmt", "run001")) +get_gradients(file.path("models", "onecmt", "run001")) -get_gradients(file.path(test_data_dir, "models", "onecmt", "run001")) +get_gradients(file.path("models", "onecmt", "run001")) -get_gradients(file.path(test_data_dir, "models", "onecmt", "run001", "run001.grd")) +get_gradients(file.path("models", "onecmt", "run001", "run001.grd")) ``` ```{r} -get_gradients(file.path(test_data_dir, "models", "onecmt", "run002")) +get_gradients(file.path("models", "onecmt", "run002")) -get_gradients(file.path(test_data_dir, "models", "onecmt", "run002.mod")) +get_gradients(file.path("models", "onecmt", "run002.mod")) -get_gradients(file.path(test_data_dir, "models", "onecmt", "run002", "run002.grd")) +get_gradients(file.path("models", "onecmt", "run002", "run002.grd")) -get_gradients(file.path(test_data_dir, "models", "onecmt", "run002_metadata.json")) +get_gradients(file.path("models", "onecmt", "run002_metadata.json")) ``` diff --git a/vignettes/hyperion_model.Rmd b/vignettes/hyperion_model.Rmd index e1228c34..5aa56523 100644 --- a/vignettes/hyperion_model.Rmd +++ b/vignettes/hyperion_model.Rmd @@ -12,27 +12,38 @@ knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) + +vignette_root <- tempfile("hyperion-vignette-") +dir.create(vignette_root, recursive = TRUE) +file.copy( + list.files(system.file("extdata", package = "hyperion"), full.names = TRUE), + vignette_root, + recursive = TRUE +) +file.copy( + system.file("pharos.toml", package = "hyperion"), + vignette_root +) +knitr::opts_knit$set(root.dir = vignette_root) ``` ```{r setup} library(hyperion) - -test_data_dir <- system.file("extdata", package = "hyperion") ``` # Hyperion Model object ```{r} -mod <- read_model(file.path(test_data_dir, "mod", "1001.mod")) +mod <- read_model(file.path("mod", "1001.mod")) mod -mod <- read_model(file.path(test_data_dir, "models", "onecmt", "run002b001.mod")) +mod <- read_model(file.path("models", "onecmt", "run002b001.mod")) mod -mod_nm <- read_model(file.path(test_data_dir, "mod", "nmexample.mod")) +mod_nm <- read_model(file.path("mod", "nmexample.mod")) mod_nm -mod_e <- read_model(file.path(test_data_dir, "mod", "everything.mod")) +mod_e <- read_model(file.path("mod", "everything.mod")) mod_e ``` @@ -43,19 +54,19 @@ attributes(mod) |> names() ``` ```{r} -read_model(file.path(test_data_dir, "models", "onecmt", "run001.mod")) |> - check_dataset() +read_model(file.path("models", "onecmt", "run001.mod")) |> + check_dataset() -read_model(file.path(test_data_dir, "models", "onecmt", "run002.mod")) |> +read_model(file.path("models", "onecmt", "run002.mod")) |> check_dataset() -read_model(file.path(test_data_dir, "models", "onecmt", "run003.mod")) |> +read_model(file.path("models", "onecmt", "run003.mod")) |> check_dataset() ``` ```{r} -read_model(file.path(test_data_dir, "models", "onecmt", "run001.mod")) |> +read_model(file.path("models", "onecmt", "run001.mod")) |> check_model() ``` @@ -63,7 +74,7 @@ read_model(file.path(test_data_dir, "models", "onecmt", "run001.mod")) |> ## model summary can be generated from model object ```{r} -mod <- read_model(file.path(test_data_dir, "models", "onecmt", "run003.mod")) +mod <- read_model(file.path("models", "onecmt", "run003.mod")) mod |> summary() ``` @@ -88,8 +99,8 @@ mod |> get_model_parameter_info() |> audit_parameter_info() # Copy model ```{r} copy_model( - from = file.path(test_data_dir, "models", "onecmt", "run003.mod"), - to = file.path(test_data_dir, "models", "onecmt", "run003b2.mod"), #copies run003 to run003b1 with jittered parameters + from = file.path("models", "onecmt", "run003.mod"), + to = file.path("models", "onecmt", "run003b2.mod"), #copies run003 to run003b1 with jittered parameters description = "Updating run003 to 003b1 with jittered params", jitter = 0.1, overwrite = TRUE, @@ -99,11 +110,11 @@ copy_model( ## Copy model accepts hyperion model object ```{r} -mod <- read_model(file.path(test_data_dir, "models", "onecmt", "run003.mod")) +mod <- read_model(file.path("models", "onecmt", "run003.mod")) -mod |> +mod |> copy_model( - to = file.path(test_data_dir, "models", "onecmt", "run003b2.mod"), + to = file.path("models", "onecmt", "run003b2.mod"), update = "all", description = "Updating run003 with mod object", overwrite = TRUE, @@ -115,7 +126,7 @@ mod |> # Model Lineage ```{r} -example_tree <- get_model_lineage(file.path(test_data_dir, "models", "onecmt")) +example_tree <- get_model_lineage(file.path("models", "onecmt")) example_tree ``` diff --git a/vignettes/lst.Rmd b/vignettes/lst.Rmd index 7f2a180b..b71ce5bb 100644 --- a/vignettes/lst.Rmd +++ b/vignettes/lst.Rmd @@ -12,19 +12,18 @@ knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) +knitr::opts_knit$set(root.dir = system.file("extdata", package = "hyperion")) ``` ```{r setup} library(hyperion) - -test_data_dir <- system.file("extdata", package = "hyperion") ``` # Parse lst ```{r} get_run_info( read_model( - file.path(test_data_dir, "models", "onecmt", "run001.mod") + file.path("models", "onecmt", "run001.mod") ) ) ``` @@ -32,7 +31,7 @@ get_run_info( ```{r} get_run_info( read_model( - file.path(test_data_dir, "models", "onecmt", "run003b1.mod") + file.path("models", "onecmt", "run003b1.mod") ) ) ``` @@ -40,7 +39,7 @@ get_run_info( ```{r} mod_sum <- summary( read_model( - file.path(test_data_dir, "models", "onecmt", "run002.mod") + file.path("models", "onecmt", "run002.mod") ) ) @@ -49,7 +48,7 @@ mod_sum ```{r} mod_sum <- summary( read_model( - file.path(test_data_dir, "models", "onecmt", "run003b1.mod") + file.path("models", "onecmt", "run003b1.mod") ) ) diff --git a/vignettes/shk.Rmd b/vignettes/shk.Rmd index f024dc03..4486eaae 100644 --- a/vignettes/shk.Rmd +++ b/vignettes/shk.Rmd @@ -12,33 +12,32 @@ knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) +knitr::opts_knit$set(root.dir = system.file("extdata", package = "hyperion")) ``` ```{r setup} library(hyperion) - -test_data_dir <- system.file("extdata", package = "hyperion") ``` ```{r} -get_eta_shrinkage(file.path(test_data_dir, "shk", "3068.shk")) +get_eta_shrinkage(file.path("shk", "3068.shk")) ``` ```{r} -get_eps_shrinkage(file.path(test_data_dir, "shk", "3068.shk")) +get_eps_shrinkage(file.path("shk", "3068.shk")) ``` ```{r} -get_eta_shrinkage(file.path(test_data_dir, "shk", "bql.shk")) +get_eta_shrinkage(file.path("shk", "bql.shk")) ``` ```{r} -get_eps_shrinkage(file.path(test_data_dir, "shk", "bql.shk")) +get_eps_shrinkage(file.path("shk", "bql.shk")) ``` ```{r} -get_eta_shrinkage(file.path(test_data_dir, "shk", "itsimp.shk")) +get_eta_shrinkage(file.path("shk", "itsimp.shk")) ``` ```{r} -get_eps_shrinkage(file.path(test_data_dir, "shk", "itsimp.shk")) +get_eps_shrinkage(file.path("shk", "itsimp.shk")) ``` From 90388987ccb5cc8c86c95b126606007c19f9bbf5 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Wed, 6 May 2026 16:53:51 -0400 Subject: [PATCH 27/41] update vignettes add tags to copy --- R/extendr-wrappers.R | 1 + man/copy_model.Rd | 2 ++ src/rust/nonmem/src/model/copy.rs | 1 + vignettes/ext.Rmd | 14 +++++++++++++- vignettes/grd.Rmd | 14 +++++++++++++- vignettes/lst.Rmd | 14 +++++++++++++- vignettes/shk.Rmd | 14 +++++++++++++- 7 files changed, 56 insertions(+), 4 deletions(-) diff --git a/R/extendr-wrappers.R b/R/extendr-wrappers.R index 79c796f6..b50b23d9 100644 --- a/R/extendr-wrappers.R +++ b/R/extendr-wrappers.R @@ -64,6 +64,7 @@ read_model_from_lst <- function(path) .Call(wrap__read_model_from_lst, path) #' @param seed integer for random number generator seed to ensure reproducible jittering #' @param description Description of model in metadata file #' @param based_on Character vector of model names/paths that this model is based on +#' @param tags Character vector of tags to attach to the model in metadata #' @param no_metadata boolean, if true, does not create metadatafile, default FALSE #' #' @return path to new model file (invisible) todo diff --git a/man/copy_model.Rd b/man/copy_model.Rd index 69769625..7a20edbb 100644 --- a/man/copy_model.Rd +++ b/man/copy_model.Rd @@ -44,6 +44,8 @@ Examples: "THETA1" or c("THETA1")} \item{based_on}{Character vector of model names/paths that this model is based on} +\item{tags}{Character vector of tags to attach to the model in metadata} + \item{no_metadata}{boolean, if true, does not create metadatafile, default FALSE} } \value{ diff --git a/src/rust/nonmem/src/model/copy.rs b/src/rust/nonmem/src/model/copy.rs index efc7eacd..c3c61b65 100644 --- a/src/rust/nonmem/src/model/copy.rs +++ b/src/rust/nonmem/src/model/copy.rs @@ -92,6 +92,7 @@ fn parse_update_robj(update: Robj) -> Result> { /// @param seed integer for random number generator seed to ensure reproducible jittering /// @param description Description of model in metadata file /// @param based_on Character vector of model names/paths that this model is based on +/// @param tags Character vector of tags to attach to the model in metadata /// @param no_metadata boolean, if true, does not create metadatafile, default FALSE /// /// @return path to new model file (invisible) todo diff --git a/vignettes/ext.Rmd b/vignettes/ext.Rmd index 1f55a81c..69e1a296 100644 --- a/vignettes/ext.Rmd +++ b/vignettes/ext.Rmd @@ -12,7 +12,19 @@ knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) -knitr::opts_knit$set(root.dir = system.file("extdata", package = "hyperion")) + +vignette_root <- tempfile("hyperion-vignette-") +dir.create(vignette_root, recursive = TRUE) +file.copy( + list.files(system.file("extdata", package = "hyperion"), full.names = TRUE), + vignette_root, + recursive = TRUE +) +file.copy( + system.file("pharos.toml", package = "hyperion"), + vignette_root +) +knitr::opts_knit$set(root.dir = vignette_root) ``` ```{r setup} diff --git a/vignettes/grd.Rmd b/vignettes/grd.Rmd index b1e51814..a094d7da 100644 --- a/vignettes/grd.Rmd +++ b/vignettes/grd.Rmd @@ -12,7 +12,19 @@ knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) -knitr::opts_knit$set(root.dir = system.file("extdata", package = "hyperion")) + +vignette_root <- tempfile("hyperion-vignette-") +dir.create(vignette_root, recursive = TRUE) +file.copy( + list.files(system.file("extdata", package = "hyperion"), full.names = TRUE), + vignette_root, + recursive = TRUE +) +file.copy( + system.file("pharos.toml", package = "hyperion"), + vignette_root +) +knitr::opts_knit$set(root.dir = vignette_root) ``` ```{r setup} diff --git a/vignettes/lst.Rmd b/vignettes/lst.Rmd index b71ce5bb..5f0af858 100644 --- a/vignettes/lst.Rmd +++ b/vignettes/lst.Rmd @@ -12,7 +12,19 @@ knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) -knitr::opts_knit$set(root.dir = system.file("extdata", package = "hyperion")) + +vignette_root <- tempfile("hyperion-vignette-") +dir.create(vignette_root, recursive = TRUE) +file.copy( + list.files(system.file("extdata", package = "hyperion"), full.names = TRUE), + vignette_root, + recursive = TRUE +) +file.copy( + system.file("pharos.toml", package = "hyperion"), + vignette_root +) +knitr::opts_knit$set(root.dir = vignette_root) ``` ```{r setup} diff --git a/vignettes/shk.Rmd b/vignettes/shk.Rmd index 4486eaae..e17ffeb9 100644 --- a/vignettes/shk.Rmd +++ b/vignettes/shk.Rmd @@ -12,7 +12,19 @@ knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) -knitr::opts_knit$set(root.dir = system.file("extdata", package = "hyperion")) + +vignette_root <- tempfile("hyperion-vignette-") +dir.create(vignette_root, recursive = TRUE) +file.copy( + list.files(system.file("extdata", package = "hyperion"), full.names = TRUE), + vignette_root, + recursive = TRUE +) +file.copy( + system.file("pharos.toml", package = "hyperion"), + vignette_root +) +knitr::opts_knit$set(root.dir = vignette_root) ``` ```{r setup} From dbbe90abd29141cc842287190c86b74b6ef00bc2 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Mon, 11 May 2026 16:29:31 -0400 Subject: [PATCH 28/41] update with pharos and config dir option. lineage rework updated --- R/extendr-wrappers.R | 38 +- R/tree-methods.R | 331 ++++++++---------- R/utils.R | 22 ++ .../models/onecmt/run001/pharos_start.json | 4 +- .../models/onecmt/run002/pharos_start.json | 4 +- .../models/onecmt/run002_metadata.json | 2 +- .../models/onecmt/run002a_metadata.json | 2 +- .../models/onecmt/run002b001_metadata.json | 8 +- .../models/onecmt/run003/pharos_start.json | 4 +- .../models/onecmt/run003_metadata.json | 2 +- .../models/onecmt/run003b1/pharos_start.json | 4 +- .../models/onecmt/run003b1_metadata.json | 2 +- .../models/onecmt/run003b2_metadata.json | 2 +- .../models/onecmt/run004/pharos_start.json | 4 +- .../models/onecmt/run004_metadata.json | 2 +- .../models/onecmt/run005_metadata.json | 2 +- .../run-error-names/run-err/pharos_start.json | 4 +- man/are_models_in_lineage.Rd | 16 +- man/get_model_ancestors.Rd | 15 +- man/get_model_descendants.Rd | 13 +- man/get_model_lineage.Rd | 37 +- pharos.toml | 30 -- rproject.toml | 2 +- src/rust/Cargo.lock | 40 +-- src/rust/Cargo.toml | 8 +- src/rust/core/src/lib.rs | 17 + src/rust/nonmem/src/model/lineage.rs | 253 +++++++------ .../_snaps/hyperion-tree-knit/tree-knit.html | 6 +- tests/testthat/_snaps/hyperion-tree-print.md | 6 +- tests/testthat/_snaps/lineage.md | 84 +++++ tests/testthat/test-hyperion-tree-knit.R | 33 +- tests/testthat/test-hyperion-tree-print.R | 33 +- tests/testthat/test-lineage-helpers.R | 151 -------- tests/testthat/test-lineage.R | 42 +++ vignettes/ext.Rmd | 14 +- vignettes/grd.Rmd | 14 +- vignettes/hyperion_model.Rmd | 2 +- vignettes/lst.Rmd | 14 +- vignettes/shk.Rmd | 14 +- 39 files changed, 622 insertions(+), 659 deletions(-) delete mode 100644 pharos.toml create mode 100644 tests/testthat/_snaps/lineage.md delete mode 100644 tests/testthat/test-lineage-helpers.R create mode 100644 tests/testthat/test-lineage.R diff --git a/R/extendr-wrappers.R b/R/extendr-wrappers.R index b50b23d9..05fed1f3 100644 --- a/R/extendr-wrappers.R +++ b/R/extendr-wrappers.R @@ -123,28 +123,34 @@ check_model <- function(model_path) .Call(wrap__check_model_wrap, model_path) #' @export check_dataset <- function(model) .Call(wrap__check_dataset, model) -#' Get's model lineage -#' -#' @param model_or_dir a hyperion_nonmem_model object (or a path to a `.mod`/`.ctl` -#' file), or a path to a directory. When a model is supplied the result is -#' filtered to that model and its ancestors only (chain leading up to the -#' model). When a directory is supplied no filter is applied. -#' @param scope `"project"` (default) walks the entire pharos project rooted at -#' `pharos.toml`; `"directory"` walks only the directory inferred from -#' `model_or_dir`. Node keys are always relative to the pharos config dir so -#' `based_on` references resolve consistently. +#' Show model lineage and relationships. +#' +#' With no arguments, returns the full project lineage tree. Supplying a +#' model path returns that model's full lineage (ancestors and descendants). +#' The `from` and `to` arguments filter the tree from a model downward, up +#' to a model, or to the slice between two models. The project is always +#' rooted at the directory containing `pharos.toml`. +#' +#' @param model Optional `hyperion_nonmem_model` object or model file path. +#' Returns the model's full lineage (ancestors and descendants). Conflicts +#' with `from`/`to`. +#' @param from Filter the tree to this model and everything downstream. +#' Accepts a `hyperion_nonmem_model` object or a model file path. +#' @param to Filter the tree to this model and everything upstream. +#' Accepts a `hyperion_nonmem_model` object or a model file path. #' #' @return hyperion_nonmem_tree S3 object #' @export #' #' @examples \dontrun{ -#' model <- read_model("model/nonmem/run001.mod") -#' get_model_lineage(model) # ancestors of run001 -#' get_model_lineage(model, scope = "directory") # ancestors, walking only model's dir -#' get_model_lineage("model/nonmem/") # full project tree -#' get_model_lineage("model/nonmem/", scope = "directory") # only models under that dir +#' get_model_lineage() # whole project +#' get_model_lineage("model/nonmem/run003.mod") # full lineage of run003 +#' get_model_lineage(from = "model/nonmem/run001.mod") # run001 and descendants +#' get_model_lineage(to = "model/nonmem/run003.mod") # run003 and ancestors +#' get_model_lineage(from = "model/nonmem/run001.mod", +#' to = "model/nonmem/run003.mod") # slice between two models #' } -get_model_lineage <- function(model_or_dir, scope = "project") .Call(wrap__get_model_lineage, model_or_dir, scope) +get_model_lineage <- function(model = NULL, from = NULL, to = NULL) .Call(wrap__get_model_lineage, model, from, to) #' Gets parameter estimates from model run #' diff --git a/R/tree-methods.R b/R/tree-methods.R index 616d6cd9..a5262a62 100644 --- a/R/tree-methods.R +++ b/R/tree-methods.R @@ -7,19 +7,30 @@ build_tree_display_parts <- function(x) { )) } - tree_data <- build_cli_tree_data(x) + # Index the Vec by name for O(1) lookup downstream. + nodes_by_name <- x$nodes + names(nodes_by_name) <- vapply(x$nodes, function(n) n$name, character(1)) + + tree_data <- build_cli_tree_data(nodes_by_name) total_models <- length(tree_data$parent) all_parents <- tree_data$parent all_children <- unlist(tree_data$children) root_nodes <- setdiff(all_parents, all_children) + # Models the caller named explicitly (positional, from, to) get + # highlighted in the print. The `focal` attribute is set by the rust + # `get_model_lineage` wrapper. + focal <- attr(x, "focal") %||% character() + focal_display <- gsub("\\.(mod|ctl)$", "", focal) + list( is_empty = FALSE, title = "Hyperion Model Tree", tree_data = tree_data, total_models = total_models, root_nodes = root_nodes, - nodes = x$nodes + nodes = nodes_by_name, + focal_display = focal_display ) } @@ -56,7 +67,9 @@ print.hyperion_nonmem_tree <- function(x, ...) { for (i in seq_along(tree_output)) { line <- tree_output[i] node_name <- gsub("^[^a-zA-Z0-9._]*", "", line) - node_key <- paste0(node_name, ".mod") + mod_key <- paste0(node_name, ".mod") + ctl_key <- paste0(node_name, ".ctl") + node_key <- if (mod_key %in% names(parts$nodes)) mod_key else ctl_key is_root <- (node_name %in% parts$root_nodes) children <- parts$tree_data$children[ @@ -65,34 +78,41 @@ print.hyperion_nonmem_tree <- function(x, ...) { is_leaf <- length(children) == 0 tree_prefix <- gsub(node_name, "", line, fixed = TRUE) + is_focal <- node_name %in% parts$focal_display + display_name <- if (is_focal) { + cli::style_bold(cli::style_underline(node_name)) + } else { + node_name + } colored_node <- if (is_root) { - cli::col_blue(cli::style_bold(node_name)) + cli::col_blue(cli::style_bold(display_name)) } else if (is_leaf) { - cli::col_green(node_name) + cli::col_green(display_name) } else { - cli::col_yellow(node_name) + cli::col_yellow(display_name) } - if ( - node_key %in% - names(parts$nodes) && - !is.null(parts$nodes[[node_key]]$description) - ) { - desc_text <- parts$nodes[[node_key]]$description + node_model <- parts$nodes[[node_key]]$model + has_tags <- !is.null(node_model) && length(node_model$tags) > 0 + has_desc <- !is.null(node_model) && + !is.null(node_model$description) && + nzchar(node_model$description) + suffix <- "" + if (has_tags) { + suffix <- paste0( + " ", + cli::col_cyan(paste(node_model$tags, collapse = ", ")) + ) + } + if (has_desc) { + desc_text <- node_model$description if (nchar(desc_text) > 50) { desc_text <- paste0(substr(desc_text, 1, 47), "...") } - final_output <- c( - final_output, - paste0( - tree_prefix, - colored_node, - cli::style_dim(paste0(" - ", desc_text)) - ) - ) - } else { - final_output <- c(final_output, paste0(tree_prefix, colored_node)) + sep <- if (has_tags) cli::style_dim(" | ") else " " + suffix <- paste0(suffix, sep, cli::style_dim(desc_text)) } + final_output <- c(final_output, paste0(tree_prefix, colored_node, suffix)) } if (root_idx < length(parts$root_nodes)) { @@ -113,24 +133,21 @@ print.hyperion_nonmem_tree <- function(x, ...) { #' @return A data frame suitable for cli::tree() #' @keywords internal #' @noRd -build_cli_tree_data <- function(hyperion_nonmem_tree) { - all_nodes <- names(hyperion_nonmem_tree$nodes) +build_cli_tree_data <- function(nodes_by_name) { + all_nodes <- names(nodes_by_name) - # Build children map and find unique nodes in one pass + # Build children map. Only treat a based_on reference as a parent edge if + # the parent is actually in the tree — pharos slices intentionally exclude + # ancestors outside the slice (e.g. `from = run002` excludes run001), so + # synthesizing those parents would put a phantom "root" above the slice. children_map <- list() - unique_nodes <- all_nodes - for (node_name in all_nodes) { - node_info <- hyperion_nonmem_tree$nodes[[node_name]] - if (length(node_info$based_on) > 0) { - parent <- node_info$based_on[[1]] - - # Add any parent to unique nodes if not already present - if (!(parent %in% unique_nodes)) { - unique_nodes <- c(parent, unique_nodes) + node_info <- nodes_by_name[[node_name]] + if (length(node_info$model$based_on) > 0) { + parent <- node_info$model$based_on[[1]] + if (!(parent %in% all_nodes)) { + next } - - # Build children map if (is.null(children_map[[parent]])) { children_map[[parent]] <- character(0) } @@ -138,6 +155,8 @@ build_cli_tree_data <- function(hyperion_nonmem_tree) { } } + unique_nodes <- all_nodes + # Ensure all nodes have entries in children_map for (node in unique_nodes) { if (is.null(children_map[[node]])) { @@ -148,9 +167,9 @@ build_cli_tree_data <- function(hyperion_nonmem_tree) { # Create result data frame data.frame( stringsAsFactors = FALSE, - parent = gsub("\\.mod$", "", unique_nodes), + parent = gsub("\\.(mod|ctl)$", "", unique_nodes), children = I(lapply(unique_nodes, function(node) { - gsub("\\.mod$", "", children_map[[node]]) + gsub("\\.(mod|ctl)$", "", children_map[[node]]) })) ) } @@ -193,6 +212,7 @@ knit_print.hyperion_nonmem_tree <- function(x, ...) { root_node, parts$tree_data, parts$nodes, + parts$focal_display, level = 0 ) output <- c(output, tree_lines) @@ -213,14 +233,22 @@ knit_print.hyperion_nonmem_tree <- function(x, ...) { #' @return Character vector of markdown lines for this subtree #' @keywords internal #' @noRd -knit_print_tree_node <- function(node_name, tree_data, nodes_info, level = 0) { +knit_print_tree_node <- function( + node_name, + tree_data, + nodes_info, + focal_display, + level = 0 +) { output <- character() # Create indentation indent <- paste(rep(" ", level), collapse = "") # Find node info - node_key <- paste0(node_name, ".mod") + mod_key <- paste0(node_name, ".mod") + ctl_key <- paste0(node_name, ".ctl") + node_key <- if (mod_key %in% names(nodes_info)) mod_key else ctl_key # Determine node type for styling all_parents <- tree_data$parent @@ -230,37 +258,51 @@ knit_print_tree_node <- function(node_name, tree_data, nodes_info, level = 0) { is_root <- (node_name %in% root_nodes) children <- tree_data$children[tree_data$parent == node_name][[1]] is_leaf <- length(children) == 0 + is_focal <- node_name %in% focal_display # Apply HTML styling based on node type + display_name <- if (is_focal) { + paste0('', node_name, '') + } else { + node_name + } styled_node <- if (is_root) { - paste0('', node_name, '') + paste0('', display_name, '') } else if (is_leaf) { - paste0('', node_name, '') + paste0('', display_name, '') } else { - paste0('', node_name, '') + paste0('', display_name, '') } - # Add description if available - if ( - node_key %in% - names(nodes_info) && - !is.null(nodes_info[[node_key]]$description) - ) { - desc_text <- nodes_info[[node_key]]$description + # Add tags and description if available + node_model <- nodes_info[[node_key]]$model + has_tags <- !is.null(node_model) && length(node_model$tags) > 0 + has_desc <- !is.null(node_model) && + !is.null(node_model$description) && + nzchar(node_model$description) + suffix <- "" + if (has_tags) { + suffix <- paste0( + ' ', + paste(node_model$tags, collapse = ", "), + '' + ) + } + if (has_desc) { + desc_text <- node_model$description if (nchar(desc_text) > 50) { desc_text <- paste0(substr(desc_text, 1, 47), "...") } - node_line <- paste0( - indent, - "- ", - styled_node, - ' - ', + sep <- if (has_tags) ' | ' else ' ' + suffix <- paste0( + suffix, + sep, + '', desc_text, '' ) - } else { - node_line <- paste0(indent, "- ", styled_node) } + node_line <- paste0(indent, "- ", styled_node, suffix) output <- c(output, node_line) @@ -271,6 +313,7 @@ knit_print_tree_node <- function(node_name, tree_data, nodes_info, level = 0) { child, tree_data, nodes_info, + focal_display, level + 1 ) output <- c(output, child_lines) @@ -284,154 +327,56 @@ knit_print_tree_node <- function(node_name, tree_data, nodes_info, level = 0) { # Lineage utility functions # ============================================================================== -#' Normalize model names with or without .mod suffix -#' -#' @param model_name Character model name -#' @param keep_suffix Logical, if TRUE preserves existing suffix or adds .mod -#' @return Normalized model name -#' @noRd -normalize_model_name <- function(model_name, keep_suffix = FALSE) { - suffix <- NULL - if (grepl("\\.mod$", model_name)) { - suffix <- ".mod" - } else if (grepl("\\.ctl$", model_name)) { - suffix <- ".ctl" - } - clean <- sub("\\.(mod|ctl)$", "", model_name) - if (keep_suffix) { - return(paste0(clean, suffix %||% ".mod")) - } - clean -} - #' Get a model's ancestors #' -#' Walk up the based_on chain to find all ancestors of a model. -#' -#' @param lineage A hyperion_nonmem_tree object from `get_model_lineage()` -#' @param model_name Character, model name (e.g., "run001" or "run001.mod") -#' @return Character vector of ancestor names (without .mod suffix), -#' ordered from parent to root. Returns empty vector if no ancestors. +#' @param mod A `hyperion_nonmem_model` object or a path to a `.mod`/`.ctl` +#' file. +#' @return Character vector of ancestor project-relative paths (with +#' extension, e.g., `"models/onecmt/run001.mod"`). Includes `mod` itself +#' alongside its ancestors. Returns empty vector if the lineage has no +#' ancestors. #' @export -get_model_ancestors <- function(lineage, model_name) { - if (!inherits(lineage, "hyperion_nonmem_tree")) { - rlang::abort("lineage must be a hyperion_nonmem_tree object") - } - - # Normalize model name (add .mod if needed) - model_key <- normalize_model_name(model_name, keep_suffix = TRUE) - - ancestors <- character(0) - current <- model_key - visited <- character(0) - - # Walk up the based_on chain - - while (TRUE) { - if (current %in% visited) { - rlang::abort(sprintf("Circular lineage detected at %s", current)) - } - visited <- c(visited, current) - node <- lineage$nodes[[current]] - if (is.null(node) || length(node$based_on) == 0) { - break - } - parent <- node$based_on[[1]] - # Normalize parent name - parent_clean <- normalize_model_name(parent) - ancestors <- c(ancestors, parent_clean) - current <- normalize_model_name(parent, keep_suffix = TRUE) - } - - ancestors +get_model_ancestors <- function(mod) { + nodes <- get_model_lineage(to = mod)$nodes + vapply(nodes, function(n) n$name, character(1)) } #' Get a model's descendants #' -#' Find all models whose based_on chain includes the given model. -#' -#' @param lineage A hyperion_nonmem_tree object from `get_model_lineage()` -#' @param model_name Character, model name (e.g., "run001" or "run001.mod") -#' @return Character vector of descendant names (without .mod suffix) +#' @param mod A `hyperion_nonmem_model` object or a path to a `.mod`/`.ctl` +#' file. +#' @return Character vector of descendant project-relative paths (with +#' extension, e.g., `"models/onecmt/run002.mod"`). Does not include +#' `mod` itself. #' @export -get_model_descendants <- function(lineage, model_name) { - if (!inherits(lineage, "hyperion_nonmem_tree")) { - rlang::abort("lineage must be a hyperion_nonmem_tree object") - } - - # Normalize model name (remove .mod if present) - model_clean <- normalize_model_name(model_name) - - descendants <- character(0) - - # Build parent -> children map once - parent_map <- list() - for (node_name in names(lineage$nodes)) { - node <- lineage$nodes[[node_name]] - if (!is.null(node) && length(node$based_on) > 0) { - parent_clean <- normalize_model_name(node$based_on[[1]]) - child_clean <- normalize_model_name(node_name) - parent_map[[parent_clean]] <- unique(c( - parent_map[[parent_clean]], - child_clean - )) - } - } - - # Traverse descendants from the starting model - queue <- model_clean - visited <- character(0) - - while (length(queue) > 0) { - current <- queue[[1]] - queue <- queue[-1] - children <- parent_map[[current]] - if (length(children) == 0) { - next - } - for (child in children) { - if (child %in% visited) { - next - } - visited <- c(visited, child) - descendants <- c(descendants, child) - queue <- c(queue, child) - } - } - - descendants +get_model_descendants <- function(mod) { + from_keys <- vapply( + get_model_lineage(from = mod)$nodes, + function(n) n$name, + character(1) + ) + # `slice(from = mod, to = mod)` resolves to just `mod` itself; strip it + # out so the result is descendants only. + self_key <- vapply( + get_model_lineage(from = mod, to = mod)$nodes, + function(n) n$name, + character(1) + ) + setdiff(from_keys, self_key) } #' Check if two models are in a direct lineage #' -#' Returns TRUE if model1 is an ancestor of model2 or vice versa -#' (i.e., they are in a direct parent-child chain). +#' Returns TRUE if `m1` is an ancestor of `m2` or vice versa (i.e., they +#' are in a direct parent-child chain). #' -#' @param lineage A hyperion_nonmem_tree object from `get_model_lineage()` -#' @param model1 Character, model name (e.g., "run001" or "run001.mod") -#' @param model2 Character, model name (e.g., "run003" or "run003.mod") -#' @return Logical, TRUE if models are in direct lineage +#' @param m1 A `hyperion_nonmem_model` object or a path to a `.mod`/`.ctl` +#' file. +#' @param m2 A `hyperion_nonmem_model` object or a path to a `.mod`/`.ctl` +#' file. +#' @return Logical, TRUE if models are in direct lineage. #' @export -are_models_in_lineage <- function(lineage, model1, model2) { - if (!inherits(lineage, "hyperion_nonmem_tree")) { - rlang::abort("lineage must be a hyperion_nonmem_tree object") - } - - # Normalize model names - model1_clean <- normalize_model_name(model1) - model2_clean <- normalize_model_name(model2) - - # Check if model1 is ancestor of model2 - ancestors2 <- get_model_ancestors(lineage, model2) - if (model1_clean %in% ancestors2) { - return(TRUE) - } - - # Check if model2 is ancestor of model1 - ancestors1 <- get_model_ancestors(lineage, model1) - if (model2_clean %in% ancestors1) { - return(TRUE) - } - - FALSE +are_models_in_lineage <- function(m1, m2) { + length(get_model_lineage(from = m1, to = m2)$nodes) > 0 || + length(get_model_lineage(from = m2, to = m1)$nodes) > 0 } diff --git a/R/utils.R b/R/utils.R index 5d2a21f7..6d0a7d18 100644 --- a/R/utils.R +++ b/R/utils.R @@ -468,6 +468,28 @@ hyperion_options_message <- function() { "\n" ) + # Show the hyperion.config_dir override status first since it controls + # where pharos.toml gets resolved from. + config_dir_value <- getOption("hyperion.config_dir") + if (!is.null(config_dir_value) && nzchar(config_dir_value)) { + msg <- paste0( + msg, + cli::col_green(cli::symbol$tick), + " ", + "hyperion.config_dir : ", + config_dir_value, + "\n" + ) + } else { + msg <- paste0( + msg, + cli::symbol$info, + " ", + cli::style_dim("hyperion.config_dir : (unset)"), + "\n" + ) + } + if (grepl("No pharos.toml config file found", pharos_config_status)) { msg <- paste0( msg, diff --git a/inst/extdata/models/onecmt/run001/pharos_start.json b/inst/extdata/models/onecmt/run001/pharos_start.json index 68be040b..13f748a8 100644 --- a/inst/extdata/models/onecmt/run001/pharos_start.json +++ b/inst/extdata/models/onecmt/run001/pharos_start.json @@ -1,9 +1,9 @@ { "start": "2026-01-16T17:08:30+00:00", "model_name": "run001", - "model_canonical_path": "/data/user-homes/matthews/Packages/hyperion/inst/extdata/test_data/models/onecmt/run001.mod", + "model_canonical_path": "/Users/mattsmith/Documents/hyperion/inst/extdata/models/onecmt/run001.mod", "dataset_path": "../../data/derived/onecmpt-oral-30ind.csv", - "dataset_canonical_path": "/data/user-homes/matthews/Packages/hyperion/inst/extdata/test_data/data/derived/onecmpt-oral-30ind.csv", + "dataset_canonical_path": "/Users/mattsmith/Documents/hyperion/inst/extdata/data/derived/onecmpt-oral-30ind.csv", "dataset_hashes": { "blake3": "8d8189cfc45dc4d56c295ca990a131e086f53d874aa91e730c1e8856e840b005" }, diff --git a/inst/extdata/models/onecmt/run002/pharos_start.json b/inst/extdata/models/onecmt/run002/pharos_start.json index 995bf078..3117ee1e 100644 --- a/inst/extdata/models/onecmt/run002/pharos_start.json +++ b/inst/extdata/models/onecmt/run002/pharos_start.json @@ -1,9 +1,9 @@ { "start": "2025-11-05T16:04:31+00:00", "model_name": "run002", - "model_canonical_path": "/data/user-homes/matthews/Packages/hyperion/vignettes/test_data/models/onecmt/run002.mod", + "model_canonical_path": "/Users/mattsmith/Documents/hyperion/inst/extdata/models/onecmt/run002.mod", "dataset_path": "../../data/derived/onecmpt-oral-30ind.csv", - "dataset_canonical_path": "/data/user-homes/matthews/Packages/hyperion/vignettes/test_data/data/derived/onecmpt-oral-30ind.csv", + "dataset_canonical_path": "/Users/mattsmith/Documents/hyperion/inst/extdata/data/derived/onecmpt-oral-30ind.csv", "dataset_hashes": { "blake3": "8d8189cfc45dc4d56c295ca990a131e086f53d874aa91e730c1e8856e840b005" }, diff --git a/inst/extdata/models/onecmt/run002_metadata.json b/inst/extdata/models/onecmt/run002_metadata.json index 585823a9..96df28ae 100644 --- a/inst/extdata/models/onecmt/run002_metadata.json +++ b/inst/extdata/models/onecmt/run002_metadata.json @@ -1,6 +1,6 @@ { "based_on": [ - "run001.mod" + "extdata/models/onecmt/run001.mod" ], "description": "Adding COV step, unfixing eps(2)", "tags": [] diff --git a/inst/extdata/models/onecmt/run002a_metadata.json b/inst/extdata/models/onecmt/run002a_metadata.json index 6ee1b664..62be3a11 100644 --- a/inst/extdata/models/onecmt/run002a_metadata.json +++ b/inst/extdata/models/onecmt/run002a_metadata.json @@ -1,6 +1,6 @@ { "based_on": [ - "run002.mod" + "extdata/models/onecmt/run002.mod" ], "description": "Some description about what makes run002a different", "tags": [] diff --git a/inst/extdata/models/onecmt/run002b001_metadata.json b/inst/extdata/models/onecmt/run002b001_metadata.json index 9c34c697..6f697b18 100644 --- a/inst/extdata/models/onecmt/run002b001_metadata.json +++ b/inst/extdata/models/onecmt/run002b001_metadata.json @@ -1,7 +1,11 @@ { "based_on": [ - "run002.mod" + "extdata/models/onecmt/run002.mod" ], + "copied_from": "", "description": "Jittering initial sigma estimates, using theta/omega final estimates. Adding covariate", - "tags": [] + "tags": [ + "not run", + "2cmt" + ] } diff --git a/inst/extdata/models/onecmt/run003/pharos_start.json b/inst/extdata/models/onecmt/run003/pharos_start.json index 4b29df55..2b2e4270 100644 --- a/inst/extdata/models/onecmt/run003/pharos_start.json +++ b/inst/extdata/models/onecmt/run003/pharos_start.json @@ -1,9 +1,9 @@ { "start": "2026-01-08T15:08:59+00:00", "model_name": "run003", - "model_canonical_path": "/data/user-homes/matthews/Packages/hyperion/vignettes/test_data/models/onecmt/run003.mod", + "model_canonical_path": "/Users/mattsmith/Documents/hyperion/inst/extdata/models/onecmt/run003.mod", "dataset_path": "../../data/derived/onecmpt-oral-30ind.csv", - "dataset_canonical_path": "/data/user-homes/matthews/Packages/hyperion/vignettes/test_data/data/derived/onecmpt-oral-30ind.csv", + "dataset_canonical_path": "/Users/mattsmith/Documents/hyperion/inst/extdata/data/derived/onecmpt-oral-30ind.csv", "dataset_hashes": { "blake3": "8d8189cfc45dc4d56c295ca990a131e086f53d874aa91e730c1e8856e840b005" }, diff --git a/inst/extdata/models/onecmt/run003_metadata.json b/inst/extdata/models/onecmt/run003_metadata.json index 1da8d7aa..98847f17 100644 --- a/inst/extdata/models/onecmt/run003_metadata.json +++ b/inst/extdata/models/onecmt/run003_metadata.json @@ -1,6 +1,6 @@ { "based_on": [ - "run002.mod" + "extdata/models/onecmt/run002.mod" ], "description": "Jittering initial estimates", "tags": [ diff --git a/inst/extdata/models/onecmt/run003b1/pharos_start.json b/inst/extdata/models/onecmt/run003b1/pharos_start.json index 054a4e5c..0e4af0f1 100644 --- a/inst/extdata/models/onecmt/run003b1/pharos_start.json +++ b/inst/extdata/models/onecmt/run003b1/pharos_start.json @@ -1,9 +1,9 @@ { "start": "2026-01-13T19:53:01+00:00", "model_name": "run003b1", - "model_canonical_path": "/data/user-homes/matthews/Packages/hyperion/vignettes/test_data/models/onecmt/run003b1.mod", + "model_canonical_path": "/Users/mattsmith/Documents/hyperion/inst/extdata/models/onecmt/run003b1.mod", "dataset_path": "../../data/derived/onecmpt-oral-30ind-cov.csv", - "dataset_canonical_path": "/data/user-homes/matthews/Packages/hyperion/vignettes/test_data/data/derived/onecmpt-oral-30ind-cov.csv", + "dataset_canonical_path": "/Users/mattsmith/Documents/hyperion/inst/extdata/data/derived/onecmpt-oral-30ind-cov.csv", "dataset_hashes": { "blake3": "df6cbcde5998d94795503b6b2dd98cfd01f10b77a7d2e527e02b00b2591f93a5" }, diff --git a/inst/extdata/models/onecmt/run003b1_metadata.json b/inst/extdata/models/onecmt/run003b1_metadata.json index e98afd84..a62ac31b 100644 --- a/inst/extdata/models/onecmt/run003b1_metadata.json +++ b/inst/extdata/models/onecmt/run003b1_metadata.json @@ -1,6 +1,6 @@ { "based_on": [ - "run003.mod" + "extdata/models/onecmt/run003.mod" ], "description": "Updating run003 to 003b1 with jittered params. Adding WT on V", "tags": [] diff --git a/inst/extdata/models/onecmt/run003b2_metadata.json b/inst/extdata/models/onecmt/run003b2_metadata.json index 7d99ba37..79ba9a7d 100644 --- a/inst/extdata/models/onecmt/run003b2_metadata.json +++ b/inst/extdata/models/onecmt/run003b2_metadata.json @@ -1,6 +1,6 @@ { "based_on": [ - "run003.mod" + "extdata/models/onecmt/run003.mod" ], "description": "Updating run003 with mod object", "tags": [] diff --git a/inst/extdata/models/onecmt/run004/pharos_start.json b/inst/extdata/models/onecmt/run004/pharos_start.json index 4b29df55..2b2e4270 100644 --- a/inst/extdata/models/onecmt/run004/pharos_start.json +++ b/inst/extdata/models/onecmt/run004/pharos_start.json @@ -1,9 +1,9 @@ { "start": "2026-01-08T15:08:59+00:00", "model_name": "run003", - "model_canonical_path": "/data/user-homes/matthews/Packages/hyperion/vignettes/test_data/models/onecmt/run003.mod", + "model_canonical_path": "/Users/mattsmith/Documents/hyperion/inst/extdata/models/onecmt/run003.mod", "dataset_path": "../../data/derived/onecmpt-oral-30ind.csv", - "dataset_canonical_path": "/data/user-homes/matthews/Packages/hyperion/vignettes/test_data/data/derived/onecmpt-oral-30ind.csv", + "dataset_canonical_path": "/Users/mattsmith/Documents/hyperion/inst/extdata/data/derived/onecmpt-oral-30ind.csv", "dataset_hashes": { "blake3": "8d8189cfc45dc4d56c295ca990a131e086f53d874aa91e730c1e8856e840b005" }, diff --git a/inst/extdata/models/onecmt/run004_metadata.json b/inst/extdata/models/onecmt/run004_metadata.json index 344c6440..5c59f5af 100644 --- a/inst/extdata/models/onecmt/run004_metadata.json +++ b/inst/extdata/models/onecmt/run004_metadata.json @@ -1,6 +1,6 @@ { "based_on": [ - "run001.mod" + "extdata/models/onecmt/run001.mod" ], "description": "Updating run001 to run004 with jittered params and updated initials", "tags": [] diff --git a/inst/extdata/models/onecmt/run005_metadata.json b/inst/extdata/models/onecmt/run005_metadata.json index 344c6440..5c59f5af 100644 --- a/inst/extdata/models/onecmt/run005_metadata.json +++ b/inst/extdata/models/onecmt/run005_metadata.json @@ -1,6 +1,6 @@ { "based_on": [ - "run001.mod" + "extdata/models/onecmt/run001.mod" ], "description": "Updating run001 to run004 with jittered params and updated initials", "tags": [] diff --git a/inst/extdata/models/run-error-names/run-err/pharos_start.json b/inst/extdata/models/run-error-names/run-err/pharos_start.json index 569c841a..bd163369 100644 --- a/inst/extdata/models/run-error-names/run-err/pharos_start.json +++ b/inst/extdata/models/run-error-names/run-err/pharos_start.json @@ -1,9 +1,9 @@ { "start": "2026-02-23T17:49:03+00:00", "model_name": "run-err", - "model_canonical_path": "/data/user-homes/matthews/Packages/hyperion/inst/extdata/models/run-error-names/run-err.mod", + "model_canonical_path": "/Users/mattsmith/Documents/hyperion/inst/extdata/models/run-error-names/run-err.mod", "dataset_path": "../../data/derived/onecmpt-oral-30ind.csv", - "dataset_canonical_path": "/data/user-homes/matthews/Packages/hyperion/inst/extdata/data/derived/onecmpt-oral-30ind.csv", + "dataset_canonical_path": "/Users/mattsmith/Documents/hyperion/inst/extdata/data/derived/onecmpt-oral-30ind.csv", "dataset_hashes": { "blake3": "8d8189cfc45dc4d56c295ca990a131e086f53d874aa91e730c1e8856e840b005" }, diff --git a/man/are_models_in_lineage.Rd b/man/are_models_in_lineage.Rd index ada30c9e..202b5ede 100644 --- a/man/are_models_in_lineage.Rd +++ b/man/are_models_in_lineage.Rd @@ -4,19 +4,19 @@ \alias{are_models_in_lineage} \title{Check if two models are in a direct lineage} \usage{ -are_models_in_lineage(lineage, model1, model2) +are_models_in_lineage(m1, m2) } \arguments{ -\item{lineage}{A hyperion_nonmem_tree object from \code{get_model_lineage()}} +\item{m1}{A \code{hyperion_nonmem_model} object or a path to a \code{.mod}/\code{.ctl} +file.} -\item{model1}{Character, model name (e.g., "run001" or "run001.mod")} - -\item{model2}{Character, model name (e.g., "run003" or "run003.mod")} +\item{m2}{A \code{hyperion_nonmem_model} object or a path to a \code{.mod}/\code{.ctl} +file.} } \value{ -Logical, TRUE if models are in direct lineage +Logical, TRUE if models are in direct lineage. } \description{ -Returns TRUE if model1 is an ancestor of model2 or vice versa -(i.e., they are in a direct parent-child chain). +Returns TRUE if \code{m1} is an ancestor of \code{m2} or vice versa (i.e., they +are in a direct parent-child chain). } diff --git a/man/get_model_ancestors.Rd b/man/get_model_ancestors.Rd index ba514ad8..bfd37051 100644 --- a/man/get_model_ancestors.Rd +++ b/man/get_model_ancestors.Rd @@ -4,17 +4,18 @@ \alias{get_model_ancestors} \title{Get a model's ancestors} \usage{ -get_model_ancestors(lineage, model_name) +get_model_ancestors(mod) } \arguments{ -\item{lineage}{A hyperion_nonmem_tree object from \code{get_model_lineage()}} - -\item{model_name}{Character, model name (e.g., "run001" or "run001.mod")} +\item{mod}{A \code{hyperion_nonmem_model} object or a path to a \code{.mod}/\code{.ctl} +file.} } \value{ -Character vector of ancestor names (without .mod suffix), -ordered from parent to root. Returns empty vector if no ancestors. +Character vector of ancestor project-relative paths (with +extension, e.g., \code{"models/onecmt/run001.mod"}). Includes \code{mod} itself +alongside its ancestors. Returns empty vector if the lineage has no +ancestors. } \description{ -Walk up the based_on chain to find all ancestors of a model. +Get a model's ancestors } diff --git a/man/get_model_descendants.Rd b/man/get_model_descendants.Rd index 2b8efb09..1e0f7fda 100644 --- a/man/get_model_descendants.Rd +++ b/man/get_model_descendants.Rd @@ -4,16 +4,17 @@ \alias{get_model_descendants} \title{Get a model's descendants} \usage{ -get_model_descendants(lineage, model_name) +get_model_descendants(mod) } \arguments{ -\item{lineage}{A hyperion_nonmem_tree object from \code{get_model_lineage()}} - -\item{model_name}{Character, model name (e.g., "run001" or "run001.mod")} +\item{mod}{A \code{hyperion_nonmem_model} object or a path to a \code{.mod}/\code{.ctl} +file.} } \value{ -Character vector of descendant names (without .mod suffix) +Character vector of descendant project-relative paths (with +extension, e.g., \code{"models/onecmt/run002.mod"}). Does not include +\code{mod} itself. } \description{ -Find all models whose based_on chain includes the given model. +Get a model's descendants } diff --git a/man/get_model_lineage.Rd b/man/get_model_lineage.Rd index e395c02d..6b83490f 100644 --- a/man/get_model_lineage.Rd +++ b/man/get_model_lineage.Rd @@ -2,33 +2,38 @@ % Please edit documentation in R/extendr-wrappers.R \name{get_model_lineage} \alias{get_model_lineage} -\title{Get's model lineage} +\title{Show model lineage and relationships.} \usage{ -get_model_lineage(model_or_dir, scope = "project") +get_model_lineage(model = NULL, from = NULL, to = NULL) } \arguments{ -\item{model_or_dir}{a hyperion_nonmem_model object (or a path to a \code{.mod}/\code{.ctl} -file), or a path to a directory. When a model is supplied the result is -filtered to that model and its ancestors only (chain leading up to the -model). When a directory is supplied no filter is applied.} +\item{model}{Optional \code{hyperion_nonmem_model} object or model file path. +Returns the model's full lineage (ancestors and descendants). Conflicts +with \code{from}/\code{to}.} -\item{scope}{\code{"project"} (default) walks the entire pharos project rooted at -\code{pharos.toml}; \code{"directory"} walks only the directory inferred from -\code{model_or_dir}. Node keys are always relative to the pharos config dir so -\code{based_on} references resolve consistently.} +\item{from}{Filter the tree to this model and everything downstream. +Accepts a \code{hyperion_nonmem_model} object or a model file path.} + +\item{to}{Filter the tree to this model and everything upstream. +Accepts a \code{hyperion_nonmem_model} object or a model file path.} } \value{ hyperion_nonmem_tree S3 object } \description{ -Get's model lineage +With no arguments, returns the full project lineage tree. Supplying a +model path returns that model's full lineage (ancestors and descendants). +The \code{from} and \code{to} arguments filter the tree from a model downward, up +to a model, or to the slice between two models. The project is always +rooted at the directory containing \code{pharos.toml}. } \examples{ \dontrun{ -model <- read_model("model/nonmem/run001.mod") -get_model_lineage(model) # ancestors of run001 -get_model_lineage(model, scope = "directory") # ancestors, walking only model's dir -get_model_lineage("model/nonmem/") # full project tree -get_model_lineage("model/nonmem/", scope = "directory") # only models under that dir +get_model_lineage() # whole project +get_model_lineage("model/nonmem/run003.mod") # full lineage of run003 +get_model_lineage(from = "model/nonmem/run001.mod") # run001 and descendants +get_model_lineage(to = "model/nonmem/run003.mod") # run003 and ancestors +get_model_lineage(from = "model/nonmem/run001.mod", + to = "model/nonmem/run003.mod") # slice between two models } } diff --git a/pharos.toml b/pharos.toml deleted file mode 100644 index 4bf966f6..00000000 --- a/pharos.toml +++ /dev/null @@ -1,30 +0,0 @@ -[nonmem] -clean_level = 1 -default_version = "nm760" -files_to_copy = [] - -[nonmem.options] -prsame = false -prcompile = false -prdefault = false -tprdefault = false -background = false -nobuild = false -maxlim = 2 - -[nonmem.versions] -nm760 = "/opt/nonmem/nm760" - -[nonmem.parallel] -mpiexec_path = "/opt/homebrew/bin/mpiexec" -enabled = false -num_cpus = 4 -timeout = 2147483647 - -[nonmem.comments] -type = "type2" -error_on_invalid = false - -[nonmem.summary] -high_correlation_threshold = 0.95 -high_condition_threshold = 1000 diff --git a/rproject.toml b/rproject.toml index 6e25037a..64f94fef 100644 --- a/rproject.toml +++ b/rproject.toml @@ -12,7 +12,7 @@ repositories = [ dependencies = [ "devtools", - { name = "rextendr", git = "https://github.com/extendr/rextendr", branch = "main" }, + { name = "rextendr", git = "https://github.com/extendr/rextendr", tag = "v0.5.0" }, { name = "covr", install_suggestions = true }, #{ name = "starlightr", git = "https://github.com/a2-ai/starlightr", branch = "main" }, diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index f8c53eae..6de593aa 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -91,9 +91,9 @@ checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "cc" -version = "1.2.61" +version = "1.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" dependencies = [ "find-msvc-tools", "shlex", @@ -141,7 +141,7 @@ dependencies = [ [[package]] name = "config" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=lineage-update#206f3105aa32cca4b6c76c417f146d5e2100a152" +source = "git+https://github.com/a2-ai/pharos?branch=lineage-rework#1ea2bc36eec55f6f5da24c837bbd906fc79061ef" dependencies = [ "anyhow", "fs-err", @@ -457,9 +457,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heck" @@ -586,7 +586,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.0", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -654,9 +654,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.97" +version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" dependencies = [ "cfg-if", "futures-util", @@ -741,7 +741,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "nonmem" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=lineage-update#206f3105aa32cca4b6c76c417f146d5e2100a152" +source = "git+https://github.com/a2-ai/pharos?branch=lineage-rework#1ea2bc36eec55f6f5da24c837bbd906fc79061ef" dependencies = [ "anyhow", "blake3", @@ -766,7 +766,7 @@ dependencies = [ [[package]] name = "nonmem-parser" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=lineage-update#206f3105aa32cca4b6c76c417f146d5e2100a152" +source = "git+https://github.com/a2-ai/pharos?branch=lineage-rework#1ea2bc36eec55f6f5da24c837bbd906fc79061ef" dependencies = [ "anyhow", "distrs", @@ -1125,7 +1125,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=lineage-update#206f3105aa32cca4b6c76c417f146d5e2100a152" +source = "git+https://github.com/a2-ai/pharos?branch=lineage-rework#1ea2bc36eec55f6f5da24c837bbd906fc79061ef" dependencies = [ "anyhow", "config", @@ -1377,7 +1377,7 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utils" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=lineage-update#206f3105aa32cca4b6c76c417f146d5e2100a152" +source = "git+https://github.com/a2-ai/pharos?branch=lineage-rework#1ea2bc36eec55f6f5da24c837bbd906fc79061ef" dependencies = [ "anyhow", "fs-err", @@ -1428,9 +1428,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.120" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" dependencies = [ "cfg-if", "once_cell", @@ -1441,9 +1441,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.120" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1451,9 +1451,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.120" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" dependencies = [ "bumpalo", "proc-macro2", @@ -1464,9 +1464,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.120" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" dependencies = [ "unicode-ident", ] diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 488eca8f..c10c2b02 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -38,10 +38,10 @@ serde = { workspace = true } extendr-api = { version = "0.9.0", features = ["serde"] } # Pharos components -nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "lineage-update" } -nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", branch = "lineage-update" } -config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "lineage-update" } -scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "lineage-update" } +nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "lineage-rework" } +nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", branch = "lineage-rework" } +config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "lineage-rework" } +scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "lineage-rework" } # Core utilities anyhow = "1.0.100" diff --git a/src/rust/core/src/lib.rs b/src/rust/core/src/lib.rs index c83933fa..e9b3a506 100644 --- a/src/rust/core/src/lib.rs +++ b/src/rust/core/src/lib.rs @@ -34,10 +34,27 @@ macro_rules! extendr_err { }; } +/// Name of the R option that overrides pharos's CWD-walk for the project +/// root. When set to a non-empty string, `find_config_dir` returns that +/// path verbatim — `pharos.toml` is not required to live there. Useful +/// for tests and scripts that don't want hyperion to depend on CWD. +pub const CONFIG_DIR_OPTION: &str = "hyperion.config_dir"; + pub fn find_config_dir() -> Result> { + if let Some(path) = config_dir_from_option() { + return Ok(Some(path)); + } pharos_find_config_dir().map_to_extendr_err("Failed to find config dir") } +/// Read the `hyperion.config_dir` R option. Returns `None` when the +/// option is unset, NULL, or an empty string. +fn config_dir_from_option() -> Option { + let opt = call!("getOption", CONFIG_DIR_OPTION).ok()?; + let s = opt.as_str().filter(|s| !s.is_empty())?; + Some(PathBuf::from(s)) +} + #[extendr] pub fn silence_panic_output() { std::panic::set_hook(Box::new(|_| {})); diff --git a/src/rust/nonmem/src/model/lineage.rs b/src/rust/nonmem/src/model/lineage.rs index 38c66751..4f9cd433 100644 --- a/src/rust/nonmem/src/model/lineage.rs +++ b/src/rust/nonmem/src/model/lineage.rs @@ -2,12 +2,14 @@ use extendr_api::Result; use extendr_api::prelude::*; use extendr_api::serializer::to_robj; +use fs_err as fs; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; +use std::path::Path; use nonmem::{LineageTree, ModelMetadata, OutputFileHash, RunEndFile, RunStartFile}; -use crate::utils::{path_from_robj, to_config_relative}; +use crate::utils::path_from_robj; use hyperion_core::{OptionExt, ResultExt, extendr_err, find_config_dir}; /// R-compatible version of RunEndFile with u128 -> f64 conversion @@ -21,20 +23,30 @@ pub struct RRunEndFile { pub output_files_hashes: Vec, } -/// R-compatible version of LineageTree -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct RLineageTree { - pub nodes: HashMap, - pub metadata: HashMap)>, - pub source_dir: String, +/// Run-specific info (start file + optional end file) for one model. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RunInfo { + pub start: RunStartFile, + pub end: Option, } -impl RLineageTree { - /// Set the source directory for this lineage tree - pub fn with_source_dir(mut self, source_dir: String) -> Self { - self.source_dir = source_dir; - self - } +/// One entry in the lineage tree. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LineageNode { + pub name: String, + pub model: ModelMetadata, + pub run: Option, +} + +/// R-compatible version of LineageTree. +/// +/// `nodes` is an ordered `Vec` so iteration order is deterministic and +/// matches pharos's `topological_order` (parents before children, +/// alphabetical tie-breaks). Each entry bundles the model metadata and the +/// optional run info, so R doesn't have to navigate parallel maps. +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct RLineageTree { + pub nodes: Vec, } impl From for RRunEndFile { @@ -52,128 +64,110 @@ impl From for RRunEndFile { impl From for RLineageTree { fn from(lineage: LineageTree) -> Self { - let r_metadata = lineage - .metadata + let all_keys: HashSet = lineage.nodes.keys().cloned().collect(); + let chain = lineage.topological_order(all_keys); + let mut project_metadata = lineage.metadata; + let nodes = chain .into_iter() - .map(|(key, (start_file, opt_end_file))| { - let r_end_file = opt_end_file.map(|end_file| end_file.into()); - (key, (start_file, r_end_file)) + .map(|(name, model)| { + let run = project_metadata + .remove(&name) + .map(|(start, end_opt)| RunInfo { + start, + end: end_opt.map(Into::into), + }); + LineageNode { name, model, run } }) .collect(); - - RLineageTree { - nodes: lineage.nodes, - metadata: r_metadata, - source_dir: String::new(), // Set by caller via with_source_dir() - } + RLineageTree { nodes } } } -/// Get's model lineage +/// Show model lineage and relationships. +/// +/// With no arguments, returns the full project lineage tree. Supplying a +/// model path returns that model's full lineage (ancestors and descendants). +/// The `from` and `to` arguments filter the tree from a model downward, up +/// to a model, or to the slice between two models. The project is always +/// rooted at the directory containing `pharos.toml`. /// -/// @param model_or_dir a hyperion_nonmem_model object (or a path to a `.mod`/`.ctl` -/// file), or a path to a directory. When a model is supplied the result is -/// filtered to that model and its ancestors only (chain leading up to the -/// model). When a directory is supplied no filter is applied. -/// @param scope `"project"` (default) walks the entire pharos project rooted at -/// `pharos.toml`; `"directory"` walks only the directory inferred from -/// `model_or_dir`. Node keys are always relative to the pharos config dir so -/// `based_on` references resolve consistently. +/// @param model Optional `hyperion_nonmem_model` object or model file path. +/// Returns the model's full lineage (ancestors and descendants). Conflicts +/// with `from`/`to`. +/// @param from Filter the tree to this model and everything downstream. +/// Accepts a `hyperion_nonmem_model` object or a model file path. +/// @param to Filter the tree to this model and everything upstream. +/// Accepts a `hyperion_nonmem_model` object or a model file path. /// /// @return hyperion_nonmem_tree S3 object /// @export /// /// @examples \dontrun{ -/// model <- read_model("model/nonmem/run001.mod") -/// get_model_lineage(model) # ancestors of run001 -/// get_model_lineage(model, scope = "directory") # ancestors, walking only model's dir -/// get_model_lineage("model/nonmem/") # full project tree -/// get_model_lineage("model/nonmem/", scope = "directory") # only models under that dir +/// get_model_lineage() # whole project +/// get_model_lineage("model/nonmem/run003.mod") # full lineage of run003 +/// get_model_lineage(from = "model/nonmem/run001.mod") # run001 and descendants +/// get_model_lineage(to = "model/nonmem/run003.mod") # run003 and ancestors +/// get_model_lineage(from = "model/nonmem/run001.mod", +/// to = "model/nonmem/run003.mod") # slice between two models /// } #[extendr] pub fn get_model_lineage( - model_or_dir: Robj, - #[extendr(default = "\"project\"")] scope: &str, + #[extendr(default = "NULL")] model: Option<&Robj>, + #[extendr(default = "NULL")] from: Option<&Robj>, + #[extendr(default = "NULL")] to: Option<&Robj>, ) -> Result { - let path = path_from_robj(&model_or_dir, false)?; - let user_passed_model = path.is_file(); - // If it's a file, use parent directory; if directory, use as-is - let model_dir = if user_passed_model { - path.parent() - .ok_or_extendr_err("Could not determine model directory")? - .to_path_buf() - } else { - path.clone() - }; + // extendr passes the R NULL default as Some(&null_robj), not None. Filter + // null Robjs out so downstream `is_some()` checks reflect user intent. + let model = model.filter(|r| !r.is_null()); + let from = from.filter(|r| !r.is_null()); + let to = to.filter(|r| !r.is_null()); + + if model.is_some() && (from.is_some() || to.is_some()) { + return Err(extendr_err!("model conflicts with from/to")); + } - // Use the pharos config dir as project_root so node keys are - // config-relative, matching how `based_on` is stored. Without this, a - // model under e.g. `struct/` walked in isolation would key as `1001.mod` - // while based_on says `struct/1001.mod` and parent links never resolve. - // Falls back to model_dir for projects without a pharos.toml. - let project_root = find_config_dir()?.unwrap_or_else(|| model_dir.clone()); - - let start = match scope { - "project" => project_root.clone(), - "directory" => model_dir.clone(), - other => { - return Err(extendr_err!( - "Invalid scope '{}': must be 'project' or 'directory'", - other - )); - } - }; + let project_root = find_config_dir()? + .ok_or_extendr_err("No pharos.toml found; lineage requires a pharos project root")?; + // Canonicalize so `load_run_metadata`'s strip_prefix against each + // model_canonical_path succeeds (pharos silently drops mismatches). + let project_root = fs::canonicalize(&project_root) + .map_to_extendr_err("Failed to canonicalize project root")?; - let lineage = LineageTree::build(&start, &project_root, true) - .map_to_extendr_err("Pharos failed to create lineage tree")?; - - // When the user passes a model file, filter the tree to just that model - // and its ancestors (the chain leading up to it). Mirrors `pharos nonmem - // lineage --to `. - let lineage = if user_passed_model { - let abs_path = - std::fs::canonicalize(&path).map_to_extendr_err("Failed to canonicalize model path")?; - let abs_root = std::fs::canonicalize(&project_root) - .map_to_extendr_err("Failed to canonicalize project root")?; - let model_key = abs_path - .strip_prefix(&abs_root) - .map_err(|_| { - extendr_err!( - "Model '{}' is outside the project root '{}'", - abs_path.display(), - abs_root.display() - ) - })? - .to_string_lossy() - .to_string(); - - let chain = lineage.get_tree_up_to(&model_key); - let chain_keys: HashSet = chain.iter().map(|(k, _)| k.clone()).collect(); - LineageTree { - nodes: lineage - .nodes - .into_iter() - .filter(|(k, _)| chain_keys.contains(k)) - .collect(), - metadata: lineage - .metadata - .into_iter() - .filter(|(k, _)| chain_keys.contains(k)) - .collect(), - } + let lineage = LineageTree::from_project_root(project_root.clone()) + .map_to_extendr_err("Pharos failed to build lineage tree")?; + + let mut focal: Vec = Vec::new(); + let chain = if let Some(model) = model { + let model_path = path_from_robj(model, false)?; + focal.push(focal_key_for(&model_path, &project_root)); + lineage + .lineage_of(&model_path) + .map_to_extendr_err("Failed to compute model lineage")? } else { + let from_path = from.map(|r| path_from_robj(r, false)).transpose()?; + let to_path = to.map(|r| path_from_robj(r, false)).transpose()?; + if let Some(p) = &from_path { + focal.push(focal_key_for(p, &project_root)); + } + if let Some(p) = &to_path { + focal.push(focal_key_for(p, &project_root)); + } lineage + .slice(from_path.as_deref(), to_path.as_deref()) + .map_to_extendr_err("Failed to slice lineage tree")? }; - // Convert to R-compatible version (u128 -> f64) and attach source directory (relative to pharos.toml) - let source_dir = to_config_relative(&model_dir)?; - let r_lineage: RLineageTree = RLineageTree::from(lineage).with_source_dir(source_dir); + let lineage = filter_lineage(lineage, chain); + + let r_lineage: RLineageTree = lineage.into(); - // Serialize R-compatible lineage to Robj let mut lineage_robj = to_robj(&r_lineage).map_to_extendr_err("Failed to create Robj from RLineageTree")?; - // Set S3 class + lineage_robj + .set_attrib("focal", focal) + .map_to_extendr_err("Failed to set focal attribute")?; + let hyperion_nonmem_tree = lineage_robj .set_class(["hyperion_nonmem_tree"]) .map_to_extendr_err("Failed to set class")?; @@ -181,6 +175,47 @@ pub fn get_model_lineage( Ok(hyperion_nonmem_tree.to_owned()) } +/// Compute the project-relative key for a user-supplied path, used to set +/// the "focal" attribute on the returned tree. Mirrors pharos's +/// `model_identity_for` resolution: canonicalize + strip the project root, +/// joining components with forward slashes. Falls back to the raw input +/// when canonicalization fails (e.g., the user passed an already-keyed +/// string). +fn focal_key_for(path: &Path, project_root: &Path) -> String { + if let Ok(canonical) = fs::canonicalize(path) + && let Ok(rel) = canonical.strip_prefix(project_root) + { + return rel + .components() + .map(|c| c.as_os_str().to_string_lossy().into_owned()) + .collect::>() + .join("/"); + } + path.to_string_lossy().into_owned() +} + +/// Trim a `LineageTree` to just the nodes in `chain`. Both `nodes` and +/// `metadata` are filtered to those keys so the downstream `From` impl sees +/// a coherent project containing only the slice. +fn filter_lineage(lineage: LineageTree, chain: Vec<(String, ModelMetadata)>) -> LineageTree { + let chain_keys: HashSet = chain.into_iter().map(|(k, _)| k).collect(); + // `project_root` is private on `LineageTree`; build via Default and + // mutate the pub fields. The downstream `From` impl only needs + // identity-keyed (string) access; it doesn't call path-based queries. + let mut filtered = LineageTree::default(); + filtered.nodes = lineage + .nodes + .into_iter() + .filter(|(k, _)| chain_keys.contains(k)) + .collect(); + filtered.metadata = lineage + .metadata + .into_iter() + .filter(|(k, _)| chain_keys.contains(k)) + .collect(); + filtered +} + extendr_module! { mod lineage; diff --git a/tests/testthat/_snaps/hyperion-tree-knit/tree-knit.html b/tests/testthat/_snaps/hyperion-tree-knit/tree-knit.html index 7e2a0607..5387fc48 100644 --- a/tests/testthat/_snaps/hyperion-tree-knit/tree-knit.html +++ b/tests/testthat/_snaps/hyperion-tree-knit/tree-knit.html @@ -3,6 +3,6 @@ ℹ️ Models: 3 -- base - Base population PK model - - run001 - Run 1 - - run002 - Run 2 with covariate effects +- base Base population PK model + - run001 Run 1 + - run002 Run 2 with covariate effects diff --git a/tests/testthat/_snaps/hyperion-tree-print.md b/tests/testthat/_snaps/hyperion-tree-print.md index fe1cbc10..bfe322ac 100644 --- a/tests/testthat/_snaps/hyperion-tree-print.md +++ b/tests/testthat/_snaps/hyperion-tree-print.md @@ -9,7 +9,7 @@ i Models: 3 Output - base - Base population PK model - \-run001 - Run 1 - \-run002 - Run 2 with covariate effects + base Base population PK model + \-run001 Run 1 + \-run002 Run 2 with covariate effects diff --git a/tests/testthat/_snaps/lineage.md b/tests/testthat/_snaps/lineage.md new file mode 100644 index 00000000..8b2732bc --- /dev/null +++ b/tests/testthat/_snaps/lineage.md @@ -0,0 +1,84 @@ +# get_model_lineage() returns the whole project tree + + Code + get_model_lineage() + Message + + + -- Hyperion Model Tree --------------------------------------------------------- + i Models: 9 + + Output + extdata/models/onecmt/run001 base | Base model + +-extdata/models/onecmt/run002 Adding COV step, unfixing eps(2) + | +-extdata/models/onecmt/run002a Some description about what makes run002a diffe... + | +-extdata/models/onecmt/run002b001 not run, 2cmt | Jittering initial sigma estimates, using theta/... + | \-extdata/models/onecmt/run003 key | Jittering initial estimates + | +-extdata/models/onecmt/run003b1 Updating run003 to 003b1 with jittered params. ... + | \-extdata/models/onecmt/run003b2 Updating run003 with mod object + +-extdata/models/onecmt/run004 Updating run001 to run004 with jittered params ... + \-extdata/models/onecmt/run005 Updating run001 to run004 with jittered params ... + +# get_model_lineage(model) returns the model's full lineage + + Code + get_model_lineage("extdata/models/onecmt/run003.mod") + Message + + + -- Hyperion Model Tree --------------------------------------------------------- + i Models: 5 + + Output + extdata/models/onecmt/run001 base | Base model + \-extdata/models/onecmt/run002 Adding COV step, unfixing eps(2) + \-extdata/models/onecmt/run003 key | Jittering initial estimates + +-extdata/models/onecmt/run003b1 Updating run003 to 003b1 with jittered params. ... + \-extdata/models/onecmt/run003b2 Updating run003 with mod object + +# get_model_lineage(from, to) slices between two models + + Code + get_model_lineage(from = "extdata/models/onecmt/run001.mod", to = "extdata/models/onecmt/run003b1.mod") + Message + + + -- Hyperion Model Tree --------------------------------------------------------- + i Models: 4 + + Output + extdata/models/onecmt/run001 base | Base model + \-extdata/models/onecmt/run002 Adding COV step, unfixing eps(2) + \-extdata/models/onecmt/run003 key | Jittering initial estimates + \-extdata/models/onecmt/run003b1 Updating run003 to 003b1 with jittered params. ... + +# lineage helpers return project-relative paths + + Code + get_model_ancestors("extdata/models/onecmt/run003b1.mod") + Output + [1] "extdata/models/onecmt/run001.mod" "extdata/models/onecmt/run002.mod" + [3] "extdata/models/onecmt/run003.mod" "extdata/models/onecmt/run003b1.mod" + +--- + + Code + get_model_descendants("extdata/models/onecmt/run001.mod") + Output + [1] "extdata/models/onecmt/run002.mod" + [2] "extdata/models/onecmt/run002a.mod" + [3] "extdata/models/onecmt/run002b001.mod" + [4] "extdata/models/onecmt/run003.mod" + [5] "extdata/models/onecmt/run003b1.mod" + [6] "extdata/models/onecmt/run003b2.mod" + [7] "extdata/models/onecmt/run004.mod" + [8] "extdata/models/onecmt/run005.mod" + +--- + + Code + are_models_in_lineage("extdata/models/onecmt/run001.mod", + "extdata/models/onecmt/run003b1.mod") + Output + [1] TRUE + diff --git a/tests/testthat/test-hyperion-tree-knit.R b/tests/testthat/test-hyperion-tree-knit.R index af99fd38..ad1e6cf5 100644 --- a/tests/testthat/test-hyperion-tree-knit.R +++ b/tests/testthat/test-hyperion-tree-knit.R @@ -2,17 +2,32 @@ test_that("hyperion_nonmem_tree knit_print works", { tree <- structure( list( nodes = list( - "base.mod" = list( - based_on = list(), - description = "Base population PK model" + list( + name = "base.mod", + model = list( + based_on = list(), + description = "Base population PK model", + tags = list() + ), + run = NULL ), - "run001.mod" = list( - based_on = list("base.mod"), - description = "Run 1" + list( + name = "run001.mod", + model = list( + based_on = list("base.mod"), + description = "Run 1", + tags = list() + ), + run = NULL ), - "run002.mod" = list( - based_on = list("run001.mod"), - description = "Run 2 with covariate effects" + list( + name = "run002.mod", + model = list( + based_on = list("run001.mod"), + description = "Run 2 with covariate effects", + tags = list() + ), + run = NULL ) ) ), diff --git a/tests/testthat/test-hyperion-tree-print.R b/tests/testthat/test-hyperion-tree-print.R index 8607e386..a91d4d63 100644 --- a/tests/testthat/test-hyperion-tree-print.R +++ b/tests/testthat/test-hyperion-tree-print.R @@ -2,17 +2,32 @@ test_that("hyperion_nonmem_tree print works", { tree <- structure( list( nodes = list( - "base.mod" = list( - based_on = list(), - description = "Base population PK model" + list( + name = "base.mod", + model = list( + based_on = list(), + description = "Base population PK model", + tags = list() + ), + run = NULL ), - "run001.mod" = list( - based_on = list("base.mod"), - description = "Run 1" + list( + name = "run001.mod", + model = list( + based_on = list("base.mod"), + description = "Run 1", + tags = list() + ), + run = NULL ), - "run002.mod" = list( - based_on = list("run001.mod"), - description = "Run 2 with covariate effects" + list( + name = "run002.mod", + model = list( + based_on = list("run001.mod"), + description = "Run 2 with covariate effects", + tags = list() + ), + run = NULL ) ) ), diff --git a/tests/testthat/test-lineage-helpers.R b/tests/testthat/test-lineage-helpers.R deleted file mode 100644 index 3c6be5af..00000000 --- a/tests/testthat/test-lineage-helpers.R +++ /dev/null @@ -1,151 +0,0 @@ -test_that("get_model_ancestors returns ancestors in order", { - tree <- structure( - list( - nodes = list( - "run001.mod" = list(based_on = list(), description = "Base model"), - "run002.mod" = list( - based_on = list("run001.mod"), - description = "Child" - ), - "run003.mod" = list( - based_on = list("run002.mod"), - description = "Grandchild" - ) - ) - ), - class = "hyperion_nonmem_tree" - ) - - # run001 has no ancestors - - expect_equal(get_model_ancestors(tree, "run001"), character(0)) - expect_equal(get_model_ancestors(tree, "run001.mod"), character(0)) - - # run002's ancestor is run001 - expect_equal(get_model_ancestors(tree, "run002"), "run001") - - # run003's ancestors are run002, run001 (parent to root order) - expect_equal(get_model_ancestors(tree, "run003"), c("run002", "run001")) -}) - -test_that("get_model_descendants returns all descendants", { - tree <- structure( - list( - nodes = list( - "run001.mod" = list(based_on = list(), description = "Base model"), - "run002.mod" = list( - based_on = list("run001.mod"), - description = "Child 1" - ), - "run003.mod" = list( - based_on = list("run001.mod"), - description = "Child 2" - ), - "run004.mod" = list( - based_on = list("run002.mod"), - description = "Grandchild" - ) - ) - ), - class = "hyperion_nonmem_tree" - ) - - # run001 has three descendants - descendants <- get_model_descendants(tree, "run001") - expect_true(all(c("run002", "run003", "run004") %in% descendants)) - - # run002 has one descendant - expect_equal(get_model_descendants(tree, "run002"), "run004") - - # run003 has no descendants - expect_equal(get_model_descendants(tree, "run003"), character(0)) - - # run004 has no descendants - - expect_equal(get_model_descendants(tree, "run004"), character(0)) -}) - -test_that("are_models_in_lineage detects ancestor-descendant relationships", { - tree <- structure( - list( - nodes = list( - "run001.mod" = list(based_on = list(), description = "Base model"), - "run002.mod" = list( - based_on = list("run001.mod"), - description = "Child 1" - ), - "run003.mod" = list( - based_on = list("run001.mod"), - description = "Child 2" - ), - "run004.mod" = list( - based_on = list("run002.mod"), - description = "Grandchild" - ) - ) - ), - class = "hyperion_nonmem_tree" - ) - - # Direct parent-child - - expect_true(are_models_in_lineage(tree, "run001", "run002")) - expect_true(are_models_in_lineage(tree, "run002", "run001")) - - # Grandparent-grandchild - expect_true(are_models_in_lineage(tree, "run001", "run004")) - expect_true(are_models_in_lineage(tree, "run004", "run001")) - - # Siblings are NOT in direct lineage - - expect_false(are_models_in_lineage(tree, "run002", "run003")) - expect_false(are_models_in_lineage(tree, "run003", "run002")) - - # Cousins are NOT in direct lineage - expect_false(are_models_in_lineage(tree, "run003", "run004")) -}) - -test_that("lineage functions handle .mod suffix correctly", { - tree <- structure( - list( - nodes = list( - "run001.mod" = list(based_on = list(), description = "Base"), - "run002.mod" = list( - based_on = list("run001.mod"), - description = "Child" - ) - ) - ), - class = "hyperion_nonmem_tree" - ) - - # With and without .mod suffix should work - expect_equal(get_model_ancestors(tree, "run002"), "run001") - expect_equal(get_model_ancestors(tree, "run002.mod"), "run001") - - expect_true(are_models_in_lineage(tree, "run001", "run002")) - expect_true(are_models_in_lineage(tree, "run001.mod", "run002.mod")) - expect_true(are_models_in_lineage(tree, "run001", "run002.mod")) -}) - -test_that("lineage functions error on invalid input", { - not_a_tree <- list(nodes = list()) - - expect_error(get_model_ancestors(not_a_tree, "run001")) - expect_error(get_model_descendants(not_a_tree, "run001")) - expect_error(are_models_in_lineage(not_a_tree, "run001", "run002")) -}) - -test_that("get_model_ancestors errors on circular lineage", { - tree <- structure( - list( - nodes = list( - "run001.mod" = list(based_on = list("run002.mod")), - "run002.mod" = list(based_on = list("run001.mod")) - ) - ), - class = "hyperion_nonmem_tree" - ) - - expect_error(get_model_ancestors(tree, "run001")) -}) diff --git a/tests/testthat/test-lineage.R b/tests/testthat/test-lineage.R new file mode 100644 index 00000000..d419de98 --- /dev/null +++ b/tests/testthat/test-lineage.R @@ -0,0 +1,42 @@ +local_project_root <- function(.envir = parent.frame()) { + # Bundled fixtures use `inst/pharos.toml` as their project root, so + # `based_on` strings in `*_metadata.json` are stored as + # `extdata/models/onecmt/...`. Point the option at the install root + # (where `pharos.toml` lives) so pharos generates matching keys. + withr::local_options( + hyperion.config_dir = system.file(package = "hyperion"), + .local_envir = .envir + ) +} + +test_that("get_model_lineage() returns the whole project tree", { + local_project_root() + expect_snapshot(get_model_lineage()) +}) + +test_that("get_model_lineage(model) returns the model's full lineage", { + local_project_root() + expect_snapshot(get_model_lineage("extdata/models/onecmt/run003.mod")) +}) + +test_that("get_model_lineage(from, to) slices between two models", { + local_project_root() + expect_snapshot( + get_model_lineage( + from = "extdata/models/onecmt/run001.mod", + to = "extdata/models/onecmt/run003b1.mod" + ) + ) +}) + +test_that("lineage helpers return project-relative paths", { + local_project_root() + expect_snapshot(get_model_ancestors("extdata/models/onecmt/run003b1.mod")) + expect_snapshot(get_model_descendants("extdata/models/onecmt/run001.mod")) + expect_snapshot( + are_models_in_lineage( + "extdata/models/onecmt/run001.mod", + "extdata/models/onecmt/run003b1.mod" + ) + ) +}) diff --git a/vignettes/ext.Rmd b/vignettes/ext.Rmd index 69e1a296..1f55a81c 100644 --- a/vignettes/ext.Rmd +++ b/vignettes/ext.Rmd @@ -12,19 +12,7 @@ knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) - -vignette_root <- tempfile("hyperion-vignette-") -dir.create(vignette_root, recursive = TRUE) -file.copy( - list.files(system.file("extdata", package = "hyperion"), full.names = TRUE), - vignette_root, - recursive = TRUE -) -file.copy( - system.file("pharos.toml", package = "hyperion"), - vignette_root -) -knitr::opts_knit$set(root.dir = vignette_root) +knitr::opts_knit$set(root.dir = system.file("extdata", package = "hyperion")) ``` ```{r setup} diff --git a/vignettes/grd.Rmd b/vignettes/grd.Rmd index a094d7da..b1e51814 100644 --- a/vignettes/grd.Rmd +++ b/vignettes/grd.Rmd @@ -12,19 +12,7 @@ knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) - -vignette_root <- tempfile("hyperion-vignette-") -dir.create(vignette_root, recursive = TRUE) -file.copy( - list.files(system.file("extdata", package = "hyperion"), full.names = TRUE), - vignette_root, - recursive = TRUE -) -file.copy( - system.file("pharos.toml", package = "hyperion"), - vignette_root -) -knitr::opts_knit$set(root.dir = vignette_root) +knitr::opts_knit$set(root.dir = system.file("extdata", package = "hyperion")) ``` ```{r setup} diff --git a/vignettes/hyperion_model.Rmd b/vignettes/hyperion_model.Rmd index 5aa56523..8c715530 100644 --- a/vignettes/hyperion_model.Rmd +++ b/vignettes/hyperion_model.Rmd @@ -126,7 +126,7 @@ mod |> # Model Lineage ```{r} -example_tree <- get_model_lineage(file.path("models", "onecmt")) +example_tree <- get_model_lineage() example_tree ``` diff --git a/vignettes/lst.Rmd b/vignettes/lst.Rmd index 5f0af858..b71ce5bb 100644 --- a/vignettes/lst.Rmd +++ b/vignettes/lst.Rmd @@ -12,19 +12,7 @@ knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) - -vignette_root <- tempfile("hyperion-vignette-") -dir.create(vignette_root, recursive = TRUE) -file.copy( - list.files(system.file("extdata", package = "hyperion"), full.names = TRUE), - vignette_root, - recursive = TRUE -) -file.copy( - system.file("pharos.toml", package = "hyperion"), - vignette_root -) -knitr::opts_knit$set(root.dir = vignette_root) +knitr::opts_knit$set(root.dir = system.file("extdata", package = "hyperion")) ``` ```{r setup} diff --git a/vignettes/shk.Rmd b/vignettes/shk.Rmd index e17ffeb9..4486eaae 100644 --- a/vignettes/shk.Rmd +++ b/vignettes/shk.Rmd @@ -12,19 +12,7 @@ knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) - -vignette_root <- tempfile("hyperion-vignette-") -dir.create(vignette_root, recursive = TRUE) -file.copy( - list.files(system.file("extdata", package = "hyperion"), full.names = TRUE), - vignette_root, - recursive = TRUE -) -file.copy( - system.file("pharos.toml", package = "hyperion"), - vignette_root -) -knitr::opts_knit$set(root.dir = vignette_root) +knitr::opts_knit$set(root.dir = system.file("extdata", package = "hyperion")) ``` ```{r setup} From 5cd10d7ea99e98d8ee8e77a46d7b38c27a9440e2 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Mon, 11 May 2026 16:53:01 -0400 Subject: [PATCH 29/41] cleanup tests and pharos.tomls with option --- src/rust/nonmem/src/utils.rs | 45 +++++++++++-------------------- tests/pharos.toml | 30 --------------------- tests/testthat/setup-config-dir.R | 9 +++++++ tests/testthat/test-lineage.R | 15 ----------- vignettes/pharos.toml | 30 --------------------- 5 files changed, 24 insertions(+), 105 deletions(-) delete mode 100644 tests/pharos.toml create mode 100644 tests/testthat/setup-config-dir.R delete mode 100644 vignettes/pharos.toml diff --git a/src/rust/nonmem/src/utils.rs b/src/rust/nonmem/src/utils.rs index fa2bc8f9..4f014833 100644 --- a/src/rust/nonmem/src/utils.rs +++ b/src/rust/nonmem/src/utils.rs @@ -3,11 +3,10 @@ use extendr_api::prelude::*; use extendr_api::serializer::to_robj; use fs_err as fs; -use std::path::Component; use std::path::{Path, PathBuf}; // pharos config and nonmem crate -use config::{CONFIG_FILENAME, CommentType, Config, NonmemConfig}; +use config::{CONFIG_FILENAME, CommentType, Config, NonmemConfig, to_root_relative}; use nonmem::Model; // hyperion core @@ -148,15 +147,26 @@ pub fn validate_model_path(input_path: impl AsRef) -> Result { Err(extendr_err!("File not found: {}", path.display())) } -/// Convert an absolute path to be relative to the pharos config directory. +/// Convert a path to a project-relative identifier (forward-slash form). +/// +/// Delegates the actual prefix-stripping to pharos's `to_root_relative` for +/// consistency with how the rest of pharos generates project-relative keys. +/// We can't call pharos's `to_config_relative` directly because it uses +/// pharos's own `find_config_dir` and so wouldn't honor the +/// `hyperion.config_dir` R option override. Canonicalizes both sides +/// here so relative inputs (resolved against CWD) line up with the +/// canonical config root before stripping. +/// /// Returns the original path if no config directory is found. pub fn to_config_relative(path: impl AsRef) -> Result { let path = path.as_ref(); let config_dir = find_config_dir().map_to_extendr_err("Failed to find config dir")?; if let Some(dir) = config_dir { - let rel = make_relative_path(&dir, path); - return Ok(rel.to_string_lossy().to_string()); + let canonical_path = fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf()); + let canonical_dir = fs::canonicalize(&dir).unwrap_or(dir); + return to_root_relative(&canonical_path, &canonical_dir) + .map_to_extendr_err("Failed to make path config-relative"); } Ok(path.to_string_lossy().to_string()) @@ -209,31 +219,6 @@ pub fn path_from_robj(input: &Robj, validate_model: bool) -> Result { } } -fn make_relative_path(base: &Path, target: &Path) -> PathBuf { - let base_components: Vec> = base.components().collect(); - let target_components: Vec> = target.components().collect(); - - if base_components.first() != target_components.first() { - return target.to_path_buf(); - } - - let mut idx = 0; - let max = base_components.len().min(target_components.len()); - while idx < max && base_components[idx] == target_components[idx] { - idx += 1; - } - - let mut rel = PathBuf::new(); - for _ in idx..base_components.len() { - rel.push(".."); - } - for comp in target_components.iter().skip(idx) { - rel.push(comp.as_os_str()); - } - - rel -} - /// Gives Some(Model) if model path is found pub fn try_parse_model(path: &str) -> Option { let path_buf = std::path::Path::new(path); diff --git a/tests/pharos.toml b/tests/pharos.toml deleted file mode 100644 index 4bf966f6..00000000 --- a/tests/pharos.toml +++ /dev/null @@ -1,30 +0,0 @@ -[nonmem] -clean_level = 1 -default_version = "nm760" -files_to_copy = [] - -[nonmem.options] -prsame = false -prcompile = false -prdefault = false -tprdefault = false -background = false -nobuild = false -maxlim = 2 - -[nonmem.versions] -nm760 = "/opt/nonmem/nm760" - -[nonmem.parallel] -mpiexec_path = "/opt/homebrew/bin/mpiexec" -enabled = false -num_cpus = 4 -timeout = 2147483647 - -[nonmem.comments] -type = "type2" -error_on_invalid = false - -[nonmem.summary] -high_correlation_threshold = 0.95 -high_condition_threshold = 1000 diff --git a/tests/testthat/setup-config-dir.R b/tests/testthat/setup-config-dir.R new file mode 100644 index 00000000..205fddfe --- /dev/null +++ b/tests/testthat/setup-config-dir.R @@ -0,0 +1,9 @@ +# Pin the project root for the test session to the install root (where +# inst/pharos.toml lives). Without this, pharos's CWD-walk lands on +# tests/pharos.toml and treats `tests/` as the project root — fixtures +# under inst/extdata/ are then "outside the project root" for +# `to_root_relative` and every read_model() call errors. +old_opts <- options( + hyperion.config_dir = system.file(package = "hyperion") +) +withr::defer(options(old_opts), teardown_env()) diff --git a/tests/testthat/test-lineage.R b/tests/testthat/test-lineage.R index d419de98..887fb355 100644 --- a/tests/testthat/test-lineage.R +++ b/tests/testthat/test-lineage.R @@ -1,26 +1,12 @@ -local_project_root <- function(.envir = parent.frame()) { - # Bundled fixtures use `inst/pharos.toml` as their project root, so - # `based_on` strings in `*_metadata.json` are stored as - # `extdata/models/onecmt/...`. Point the option at the install root - # (where `pharos.toml` lives) so pharos generates matching keys. - withr::local_options( - hyperion.config_dir = system.file(package = "hyperion"), - .local_envir = .envir - ) -} - test_that("get_model_lineage() returns the whole project tree", { - local_project_root() expect_snapshot(get_model_lineage()) }) test_that("get_model_lineage(model) returns the model's full lineage", { - local_project_root() expect_snapshot(get_model_lineage("extdata/models/onecmt/run003.mod")) }) test_that("get_model_lineage(from, to) slices between two models", { - local_project_root() expect_snapshot( get_model_lineage( from = "extdata/models/onecmt/run001.mod", @@ -30,7 +16,6 @@ test_that("get_model_lineage(from, to) slices between two models", { }) test_that("lineage helpers return project-relative paths", { - local_project_root() expect_snapshot(get_model_ancestors("extdata/models/onecmt/run003b1.mod")) expect_snapshot(get_model_descendants("extdata/models/onecmt/run001.mod")) expect_snapshot( diff --git a/vignettes/pharos.toml b/vignettes/pharos.toml deleted file mode 100644 index f41f9d85..00000000 --- a/vignettes/pharos.toml +++ /dev/null @@ -1,30 +0,0 @@ -[nonmem] -clean_level = 1 -default_version = "nm760" -files_to_copy = [] - -[nonmem.options] -prsame = false -prcompile = false -prdefault = false -tprdefault = false -background = false -nobuild = false -maxlim = 2 - -[nonmem.versions] -nm760 = "/opt/nonmem/nm760" - -[nonmem.parallel] -mpiexec_path = "/opt/homebrew/bin/mpiexec" -enabled = false -num_cpus = 4 -timeout = 2147483647 - -[nonmem.comments] -type = "type1" -error_on_invalid = false - -[nonmem.summary] -high_correlation_threshold = 0.95 -high_condition_threshold = 1000 From 8acdb4d1abc4958fbdea1e0a8964b91db240c7fa Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Tue, 12 May 2026 10:27:21 -0400 Subject: [PATCH 30/41] add model_file to summary --- R/model-methods.R | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/R/model-methods.R b/R/model-methods.R index 17a8d652..6c2bf075 100644 --- a/R/model-methods.R +++ b/R/model-methods.R @@ -89,26 +89,24 @@ summary.hyperion_nonmem_model <- function( run_status <- refresh_run_status(object) - # Handle "not_run" status - return informative summary instead of error - if (identical(run_status, "not_run")) { - return(build_not_run_summary(object)) - } - - if (identical(run_status, "running")) { - return(build_running_summary(object, n_iterations)) - } - - if (!identical(run_status, "run")) { + result <- if (identical(run_status, "not_run")) { + build_not_run_summary(object) + } else if (identical(run_status, "running")) { + build_running_summary(object, n_iterations) + } else if (identical(run_status, "run")) { + get_model_summary( + object, + hide_off_diagonal_params = hide_off_diagonal_params + ) + } else { rlang::abort(paste0( "model run_status must be 'run', 'running', or 'not_run', got: ", run_status )) } - get_model_summary( - object, - hide_off_diagonal_params = hide_off_diagonal_params - ) + result$model_file <- from_config_relative(attr(object, "model_source")) + result } #' Build summary object for a running model From 13f2e1829d412df3488a79803640bdd05c5ab869 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Tue, 12 May 2026 21:46:03 -0400 Subject: [PATCH 31/41] update use_comments. touch up vignettes --- NAMESPACE | 1 + R/hyperion-package.R | 2 +- R/pharos-config.R | 23 ++++++++--- man/hyperion-package.Rd | 2 +- man/use_comments.Rd | 29 ++++++++++++++ man/use_type1_comments.Rd | 23 ----------- vignettes/hyperion_model.Rmd | 77 ++++++++++++++++++++++++++++++++++-- 7 files changed, 123 insertions(+), 34 deletions(-) create mode 100644 man/use_comments.Rd delete mode 100644 man/use_type1_comments.Rd diff --git a/NAMESPACE b/NAMESPACE index 44891683..7da653e8 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -74,6 +74,7 @@ export(submit_model_to_slurm) export(transform_value) export(update_metadata_file) export(update_param_info) +export(use_comments) export(use_type1_comments) if (getRversion() < "4.3.0") importFrom("S7", "@") importFrom(knitr,knit_print) diff --git a/R/hyperion-package.R b/R/hyperion-package.R index 194b23f0..f91b7d11 100644 --- a/R/hyperion-package.R +++ b/R/hyperion-package.R @@ -85,7 +85,7 @@ #' \item [init()] - Initialize pharos with config file path #' \item [get_pharos_config()] - Get current pharos configuration #' \item [get_comment_type()] - Get comment parsing mode (type1 or type2) -#' \item [use_type1_comments()] - Configure pharos.toml for type1 comment parsing +#' \item [use_comments()] - Configure pharos.toml comment parsing type #' } #' #' @section Metadata Files: diff --git a/R/pharos-config.R b/R/pharos-config.R index 6a121c3c..e1b8bca8 100644 --- a/R/pharos-config.R +++ b/R/pharos-config.R @@ -1,16 +1,20 @@ -#' Set comment type to type1 in pharos.toml +#' Set comment parsing type in pharos.toml #' -#' Modifies the pharos.toml configuration file to use type1 comment parsing. -#' This is useful when NONMEM control streams use the type1 comment format. +#' Writes `type = ` under the `[nonmem.comments]` section of +#' `pharos.toml`. #' +#' @param type Comment parsing type. One of `"type1"` (strict structured) or +#' `"type2"` (flexible structured grammar). #' @param path Path to pharos.toml. If NULL, finds it automatically. #' @return The path to the modified pharos.toml file (invisibly). #' @export #' #' @examples \dontrun{ -#' use_type1_comments() +#' use_comments("type2") #' } -use_type1_comments <- function(path = NULL) { +use_comments <- function(type = c("type1", "type2"), path = NULL) { + type <- match.arg(type) + if (is.null(path)) { path <- find_pharos_config_file() if (grepl("No pharos.toml", path)) { @@ -20,9 +24,16 @@ use_type1_comments <- function(path = NULL) { toml <- tomledit::read_toml(path) nonmem <- tomledit::get_item(toml, "nonmem") - nonmem$comments$type <- "type1" + nonmem$comments$type <- type toml <- tomledit::insert_items(toml, nonmem = nonmem) tomledit::write_toml(toml, path) invisible(path) } + +#' @rdname use_comments +#' @export +use_type1_comments <- function(path = NULL) { + .Deprecated("use_comments", old = "use_type1_comments") + use_comments(type = "type1", path = path) +} diff --git a/man/hyperion-package.Rd b/man/hyperion-package.Rd index 521aab7f..238b4850 100644 --- a/man/hyperion-package.Rd +++ b/man/hyperion-package.Rd @@ -105,7 +105,7 @@ Functions for pharos configuration: \item \code{\link[=init]{init()}} - Initialize pharos with config file path \item \code{\link[=get_pharos_config]{get_pharos_config()}} - Get current pharos configuration \item \code{\link[=get_comment_type]{get_comment_type()}} - Get comment parsing mode (type1 or type2) -\item \code{\link[=use_type1_comments]{use_type1_comments()}} - Configure pharos.toml for type1 comment parsing +\item \code{\link[=use_comments]{use_comments()}} - Configure pharos.toml comment parsing type } } diff --git a/man/use_comments.Rd b/man/use_comments.Rd new file mode 100644 index 00000000..567a0685 --- /dev/null +++ b/man/use_comments.Rd @@ -0,0 +1,29 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/pharos-config.R +\name{use_comments} +\alias{use_comments} +\alias{use_type1_comments} +\title{Set comment parsing type in pharos.toml} +\usage{ +use_comments(type = c("type1", "type2"), path = NULL) + +use_type1_comments(path = NULL) +} +\arguments{ +\item{type}{Comment parsing type. One of \code{"type1"} (strict structured) or +\code{"type2"} (flexible structured grammar).} + +\item{path}{Path to pharos.toml. If NULL, finds it automatically.} +} +\value{ +The path to the modified pharos.toml file (invisibly). +} +\description{ +Writes \verb{type = } under the \verb{[nonmem.comments]} section of +\code{pharos.toml}. +} +\examples{ +\dontrun{ +use_comments("type2") +} +} diff --git a/man/use_type1_comments.Rd b/man/use_type1_comments.Rd deleted file mode 100644 index 5c3b86d8..00000000 --- a/man/use_type1_comments.Rd +++ /dev/null @@ -1,23 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/pharos-config.R -\name{use_type1_comments} -\alias{use_type1_comments} -\title{Set comment type to type1 in pharos.toml} -\usage{ -use_type1_comments(path = NULL) -} -\arguments{ -\item{path}{Path to pharos.toml. If NULL, finds it automatically.} -} -\value{ -The path to the modified pharos.toml file (invisibly). -} -\description{ -Modifies the pharos.toml configuration file to use type1 comment parsing. -This is useful when NONMEM control streams use the type1 comment format. -} -\examples{ -\dontrun{ -use_type1_comments() -} -} diff --git a/vignettes/hyperion_model.Rmd b/vignettes/hyperion_model.Rmd index 8c715530..49dd1fb2 100644 --- a/vignettes/hyperion_model.Rmd +++ b/vignettes/hyperion_model.Rmd @@ -123,10 +123,81 @@ mod |> ``` -# Model Lineage +# Managing metadata and lineage + +## Set and read metadata + +`set_metadata_file()` writes a JSON metadata sidecar next to a model. The +`description`, `tags`, `based_on`, and `copied_from` fields are stored +separately so model provenance can be tracked explicitly. + +```{r} +set_metadata_file( + file.path("models", "onecmt", "run003.mod"), + description = "Base one-compartment oral absorption model", + tags = c("base", "key"), + based_on = c("run002.mod") +) + +read_model(file.path("models", "onecmt", "run003.mod")) |> + get_model_metadata() +``` + +## Populate metadata at copy time + +`copy_model()` accepts `based_on` and `tags` so the new model's metadata is +populated as part of the copy. ```{r} -example_tree <- get_model_lineage() +copy_model( + from = file.path("models", "onecmt", "run003.mod"), + to = file.path("models", "onecmt", "run003b2.mod"), + description = "run003 with jittered params, exploring WT on V", + based_on = c("run003.mod"), + tags = c("exploratory", "wt-on-v"), + jitter = 0.1, + overwrite = TRUE, + seed = 804 +) +``` + +## Clear metadata fields + +`clear_metadata_file()` selectively clears `based_on`, `copied_from`, and/or +`tags`. Fields not selected are preserved. -example_tree +```{r} +clear_metadata_file( + file.path("models", "onecmt", "run003b2.mod"), + tags = TRUE +) +``` + +## Lineage queries + +`get_model_lineage()` returns the project lineage tree. With no arguments +it returns every model rooted at the directory containing `pharos.toml`. + +```{r} +get_model_lineage() +``` + +Pass a model to get its full lineage (ancestors and descendants): + +```{r} +get_model_lineage(file.path("models", "onecmt", "run003.mod")) +``` + +Use `from` and `to` to filter the tree downward, upward, or to the slice +between two models: + +```{r} +get_model_lineage(from = file.path("models", "onecmt", "run001.mod")) + +get_model_lineage(to = file.path("models", "onecmt", "run003b1.mod")) + +get_model_lineage( + from = file.path("models", "onecmt", "run001.mod"), + to = file.path("models", "onecmt", "run003b1.mod") +) ``` From faa20e96f070be37acfb849eca9d01c98c150d3a Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Wed, 13 May 2026 11:24:16 -0400 Subject: [PATCH 32/41] update for pharos resolve ext --- src/rust/Cargo.lock | 10 +++---- src/rust/Cargo.toml | 8 ++--- src/rust/nonmem/src/model/copy.rs | 19 +++++++----- src/rust/nonmem/src/model/parameters.rs | 26 +++++++++++++++-- src/rust/nonmem/src/model/run_status.rs | 39 +++++++++++++++++++++++-- src/rust/nonmem/src/output_files/ext.rs | 38 ++++++++++++++++++++++-- src/rust/nonmem/src/utils.rs | 13 +++++++++ 7 files changed, 127 insertions(+), 26 deletions(-) diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 6de593aa..b15a56e4 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -141,7 +141,7 @@ dependencies = [ [[package]] name = "config" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=lineage-rework#1ea2bc36eec55f6f5da24c837bbd906fc79061ef" +source = "git+https://github.com/a2-ai/pharos?branch=copy-ext#4f942e66afc5f6d39fa5af416bf556f40c3945c3" dependencies = [ "anyhow", "fs-err", @@ -741,7 +741,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "nonmem" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=lineage-rework#1ea2bc36eec55f6f5da24c837bbd906fc79061ef" +source = "git+https://github.com/a2-ai/pharos?branch=copy-ext#4f942e66afc5f6d39fa5af416bf556f40c3945c3" dependencies = [ "anyhow", "blake3", @@ -766,7 +766,7 @@ dependencies = [ [[package]] name = "nonmem-parser" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=lineage-rework#1ea2bc36eec55f6f5da24c837bbd906fc79061ef" +source = "git+https://github.com/a2-ai/pharos?branch=copy-ext#4f942e66afc5f6d39fa5af416bf556f40c3945c3" dependencies = [ "anyhow", "distrs", @@ -1125,7 +1125,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=lineage-rework#1ea2bc36eec55f6f5da24c837bbd906fc79061ef" +source = "git+https://github.com/a2-ai/pharos?branch=copy-ext#4f942e66afc5f6d39fa5af416bf556f40c3945c3" dependencies = [ "anyhow", "config", @@ -1377,7 +1377,7 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utils" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=lineage-rework#1ea2bc36eec55f6f5da24c837bbd906fc79061ef" +source = "git+https://github.com/a2-ai/pharos?branch=copy-ext#4f942e66afc5f6d39fa5af416bf556f40c3945c3" dependencies = [ "anyhow", "fs-err", diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index c10c2b02..5bc64f49 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -38,10 +38,10 @@ serde = { workspace = true } extendr-api = { version = "0.9.0", features = ["serde"] } # Pharos components -nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "lineage-rework" } -nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", branch = "lineage-rework" } -config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "lineage-rework" } -scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "lineage-rework" } +nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "copy-ext" } +nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", branch = "copy-ext" } +config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "copy-ext" } +scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "copy-ext" } # Core utilities anyhow = "1.0.100" diff --git a/src/rust/nonmem/src/model/copy.rs b/src/rust/nonmem/src/model/copy.rs index c3c61b65..c8ac290e 100644 --- a/src/rust/nonmem/src/model/copy.rs +++ b/src/rust/nonmem/src/model/copy.rs @@ -1,11 +1,12 @@ use extendr_api::Result; use extendr_api::prelude::*; +use fs_err as fs; use nonmem::copy::UpdateType; -use nonmem::{CopyOptions, copy_model}; +use nonmem::{CopyOptions, Model, copy_model}; use std::path::{Path, PathBuf}; -use crate::utils::path_from_robj; +use crate::utils::{path_from_robj, resolve_ext_path}; use hyperion_core::{OptionExt, ResultExt, extendr_err}; // This should move to Option @@ -164,17 +165,19 @@ pub fn copy_model_wrap( let ext_path = match &ext_file { Some(path) => PathBuf::from(path), None => { - // Default: run001.mod -> run001/run001.ext let model_stem = from_path .file_stem() .ok_or_extendr_err("Could not determine model file stem")? - .to_string_lossy(); - - from_path + .to_string_lossy() + .to_string(); + let run_dir = from_path .parent() .ok_or_extendr_err("Could not determine parent directory")? - .join(&*model_stem) - .join(format!("{}.ext", model_stem)) + .join(&model_stem); + let content = fs::read_to_string(&from_path).map_to_extendr_err("")?; + let model = Model::parse(&content) + .map_err(|_| extendr_err!("Failed to parse model: {}", from_path.display()))?; + resolve_ext_path(&model, &run_dir, &model_stem) } }; diff --git a/src/rust/nonmem/src/model/parameters.rs b/src/rust/nonmem/src/model/parameters.rs index 31cb58d4..05684c87 100644 --- a/src/rust/nonmem/src/model/parameters.rs +++ b/src/rust/nonmem/src/model/parameters.rs @@ -16,9 +16,9 @@ use crate::{ model::robj_to_model, output_files::ext::create_ext_reader, output_files::{OMEGA, ParameterRow, ParameterRowBuilder, SIGMA, THETA, build_parameters_df}, - utils::{find_output_file, get_comment_type, path_from_robj}, + utils::{find_output_file, get_comment_type, path_from_robj, resolve_ext_path}, }; -use hyperion_core::{ResultExt, extendr_err}; +use hyperion_core::{OptionExt, ResultExt, extendr_err}; /// Extract numeric indices from a parameter name for sorting. /// @@ -133,7 +133,6 @@ pub fn get_parameters( Err(_) => Vec::new(), }; - let ext_path = find_output_file(&search_path, "ext")?; let model_path = find_output_file(&search_path, "mod").or_else(|_| find_output_file(&search_path, "ctl"))?; let content = fs::read_to_string(&model_path).map_to_extendr_err("")?; @@ -150,6 +149,27 @@ pub fn get_parameters( } }; + let stem = model_path + .file_stem() + .ok_or_extendr_err("Could not determine model file stem")? + .to_string_lossy() + .to_string(); + let run_dir = if search_path.is_dir() { + search_path.clone() + } else { + search_path + .parent() + .ok_or_extendr_err("Could not determine parent directory")? + .join(&stem) + }; + let ext_path = resolve_ext_path(&model, &run_dir, &stem); + if !ext_path.exists() { + return Err(extendr_err!( + "Output file not found: {}", + ext_path.display() + )); + } + let comment_type = get_comment_type(); let parameter_names = model .get_parameter_names(comment_type) diff --git a/src/rust/nonmem/src/model/run_status.rs b/src/rust/nonmem/src/model/run_status.rs index d60a4659..9e03f2cb 100644 --- a/src/rust/nonmem/src/model/run_status.rs +++ b/src/rust/nonmem/src/model/run_status.rs @@ -1,13 +1,15 @@ use std::fmt; -use std::path::Path; +use std::path::{Path, PathBuf}; use extendr_api::Result; use extendr_api::prelude::*; +use fs_err as fs; use hyperion_core::{OptionExt, extendr_err}; +use nonmem::Model; use nonmem::output_files::ext::ExtReader; -use crate::utils::{find_output_file, path_from_robj}; +use crate::utils::{find_output_file, path_from_robj, resolve_ext_path}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RunStatus { @@ -46,7 +48,7 @@ pub fn determine_run_status(path: impl AsRef) -> Result { return Ok(RunStatus::NotRun); } - let ext_path = run_dir.join(format!("{}.ext", stem)); + let ext_path = resolved_ext_for_run(path, &run_dir, &stem); let lst_path = run_dir.join(format!("{}.lst", stem)); let ext_exists = ext_path.exists(); @@ -68,6 +70,37 @@ pub fn determine_run_status(path: impl AsRef) -> Result { Ok(RunStatus::Running) } +/// Resolve the .ext path for a run, honoring `$EST FILE=` when the source +/// model can be parsed. Falls back to `{stem}.ext` in `run_dir` otherwise. +fn resolved_ext_for_run(input_path: &Path, run_dir: &Path, stem: &str) -> PathBuf { + let default = run_dir.join(format!("{stem}.ext")); + + // Locate the source .mod/.ctl. For .mod/.ctl input use it directly; for + // .lst input (which lives inside run_dir) the source is one level up. + let source = match input_path.extension().and_then(|e| e.to_str()) { + Some("mod") | Some("ctl") => input_path.to_path_buf(), + _ => match run_dir.parent() { + Some(p) => { + let mod_candidate = p.join(format!("{stem}.mod")); + if mod_candidate.exists() { + mod_candidate + } else { + p.join(format!("{stem}.ctl")) + } + } + None => return default, + }, + }; + + let Ok(content) = fs::read_to_string(&source) else { + return default; + }; + let Ok(model) = Model::parse(&content) else { + return default; + }; + resolve_ext_path(&model, run_dir, stem) +} + /// Determine run status for a model path, run directory, or model object. /// /// @param input A hyperion_nonmem_model object, run directory, or model path. diff --git a/src/rust/nonmem/src/output_files/ext.rs b/src/rust/nonmem/src/output_files/ext.rs index c09afa3b..633b2180 100644 --- a/src/rust/nonmem/src/output_files/ext.rs +++ b/src/rust/nonmem/src/output_files/ext.rs @@ -1,14 +1,16 @@ use extendr_api::Result; use extendr_api::{Robj, prelude::*}; +use fs_err as fs; use std::ffi::OsStr; use std::path::{Path, PathBuf}; //pharos nonmem crate +use nonmem::Model; use nonmem::estimation; use nonmem::output_files::ext::{EstimationTable, ExtReader}; -use crate::utils::{find_output_file, to_syntactic_name}; +use crate::utils::{find_output_file, resolve_ext_path, to_syntactic_name}; use hyperion_core::{OptionExt, ResultExt, extendr_err}; /// Extract .ext files from path (single file or directory) @@ -244,9 +246,39 @@ pub fn read_ext_file( #[extendr(default = "TRUE")] only_last: Option, ) -> Result { let ext_reader = create_ext_reader(line_prefixes, parameters_only, only_method, only_last)?; - let path = find_output_file(path, "ext")?; + let input = Path::new(path); + let ext_path = if input.extension() == Some(OsStr::new("ext")) { + find_output_file(input, "ext")? + } else { + let model_path = + find_output_file(input, "mod").or_else(|_| find_output_file(input, "ctl"))?; + let content = fs::read_to_string(&model_path).map_to_extendr_err("")?; + let model = Model::parse(&content) + .map_err(|_| extendr_err!("Failed to parse model: {}", model_path.display()))?; + let stem = model_path + .file_stem() + .ok_or_extendr_err("Could not determine model file stem")? + .to_string_lossy() + .to_string(); + let run_dir = if input.is_dir() { + input.to_path_buf() + } else { + input + .parent() + .ok_or_extendr_err("Could not determine parent directory")? + .join(&stem) + }; + let resolved = resolve_ext_path(&model, &run_dir, &stem); + if !resolved.exists() { + return Err(extendr_err!( + "Output file not found: {}", + resolved.display() + )); + } + resolved + }; - let tables = ext_reader.parse_file(path).map_to_extendr_err("")?; + let tables = ext_reader.parse_file(ext_path).map_to_extendr_err("")?; estimation_tables_to_dataframe(tables) } diff --git a/src/rust/nonmem/src/utils.rs b/src/rust/nonmem/src/utils.rs index 4f014833..303ec410 100644 --- a/src/rust/nonmem/src/utils.rs +++ b/src/rust/nonmem/src/utils.rs @@ -97,6 +97,19 @@ pub fn find_output_file(input_path: impl AsRef, extension: &str) -> Result } } +/// Resolve the final `.ext` file path for a model, honoring `$EST FILE=`. +/// +/// Returns the `.ext` path that the *last* `$EST` writes to, after applying +/// NONMEM's inheritance rule (an `$EST` without `FILE=` continues writing to +/// the previous `$EST`'s file). Falls back to `{stem}.ext` in `run_dir` when +/// the model has no `$EST FILE=` overrides. +pub fn resolve_ext_path(model: &Model, run_dir: &Path, stem: &str) -> PathBuf { + let default = run_dir.join(format!("{stem}.ext")); + nonmem::output_files::resolve_estimation_files(model, run_dir, &default) + .pop() + .unwrap_or(default) +} + /// Validate and resolve a model input path (.mod or .ctl). /// Returns error if path is not a .mod/.ctl file or doesn't exist. pub fn validate_model_path(input_path: impl AsRef) -> Result { From 2e4c3ea7c92b298ecc7d39f97e8e32616bc4c7c1 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Thu, 14 May 2026 09:48:46 -0400 Subject: [PATCH 33/41] Update lineage print out. fix NAMESPACE and roxygen --- DESCRIPTION | 2 +- NAMESPACE | 26 +- NEWS.md | 61 ++++ R/comments-audit.R | 2 +- R/dataset.R | 2 +- R/metadata-methods.R | 2 +- R/model-methods.R | 12 +- R/summary-methods.R | 6 +- R/tree-methods.R | 299 +++++++++++++++++-- man/get_model_lineage.Rd | 11 +- man/hyperion-package.Rd | 1 + man/knit_print.hyperion_nonmem_tree.Rd | 4 +- man/print.hyperion_nonmem_tree.Rd | 9 +- src/rust/Cargo.lock | 10 +- src/rust/Cargo.toml | 8 +- tests/testthat/_snaps/hyperion-tree-print.md | 33 ++ tests/testthat/_snaps/lineage.md | 26 +- tests/testthat/test-hyperion-tree-print.R | 77 +++++ 18 files changed, 506 insertions(+), 85 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index d1d29727..73127066 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -17,7 +17,6 @@ Description: Hyperion is an R interface to the pharos CLI for License: MIT + file LICENSE Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.3 Config/rextendr/version: 0.5.0 Config/build/never-clean: true Config/build/extra-sources: src/rust/Cargo.lock @@ -42,3 +41,4 @@ Imports: tomledit Config/testthat/edition: 3 URL: https://a2-ai.github.io/hyperion-docs, https://github.com/a2-ai/hyperion +Config/roxygen2/version: 8.0.0 diff --git a/NAMESPACE b/NAMESPACE index 7da653e8..c8257788 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,17 +1,17 @@ # Generated by roxygen2: do not edit by hand -S3method(base::`$`, hyperion_nonmem_model) -S3method(base::`[[`, hyperion_nonmem_model) -S3method(base::names, hyperion_nonmem_model) -S3method(base::print, hyperion_model_metadata) -S3method(base::print, hyperion_nonmem_dataset) -S3method(base::print, hyperion_nonmem_model) -S3method(base::print, hyperion_nonmem_summary) -S3method(base::print, hyperion_nonmem_summary_not_run) -S3method(base::print, hyperion_nonmem_summary_running) -S3method(base::print, hyperion_nonmem_tree) -S3method(base::print, parameter_audit) -S3method(base::summary, hyperion_nonmem_model) +S3method(base::`$`,hyperion_nonmem_model) +S3method(base::`[[`,hyperion_nonmem_model) +S3method(base::names,hyperion_nonmem_model) +S3method(base::print,hyperion_model_metadata) +S3method(base::print,hyperion_nonmem_dataset) +S3method(base::print,hyperion_nonmem_model) +S3method(base::print,hyperion_nonmem_summary) +S3method(base::print,hyperion_nonmem_summary_not_run) +S3method(base::print,hyperion_nonmem_summary_running) +S3method(base::print,hyperion_nonmem_tree) +S3method(base::print,parameter_audit) +S3method(base::summary,hyperion_nonmem_model) S3method(knitr::knit_print,hyperion_model_metadata) S3method(knitr::knit_print,hyperion_nonmem_dataset) S3method(knitr::knit_print,hyperion_nonmem_model) @@ -20,7 +20,7 @@ S3method(knitr::knit_print,hyperion_nonmem_summary_not_run) S3method(knitr::knit_print,hyperion_nonmem_summary_running) S3method(knitr::knit_print,hyperion_nonmem_tree) S3method(knitr::knit_print,parameter_audit) -S3method(utils::str, hyperion_nonmem_model) +S3method(utils::str,hyperion_nonmem_model) export(ModelComments) export(OmegaComment) export(SigmaComment) diff --git a/NEWS.md b/NEWS.md index a87171c7..a89c8ad6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,64 @@ +# hyperion 0.5.0 + +## Breaking changes + +- `get_model_lineage()` signature changed. Old: `get_model_lineage(model_dir)` + (required a directory or model object). New: `get_model_lineage(model = NULL, + from = NULL, to = NULL)`. + - No-arg call returns the whole project lineage tree, rooted at the directory + containing `pharos.toml`. + - `model` returns ancestors and descendants of one model. + - `from`/`to` filter the tree downstream / upstream / to the slice between + two models. + - The `model_dir` argument no longer exists. +- `copy_model()` `description` is now required (was `description = NULL`). +- R-side raw comment parsing is removed. All comment parsing is owned by + pharos. The "raw" mode documentation, parsing pipeline, and transform + keyword mapping are gone from `get_model_parameter_info()`. `NA` names are + acceptable when pharos cannot parse a comment; `summary()` falls back to + NONMEM names in that case. Raw parsing mode is most similar to `type2` + comments in pharos. +- `use_type1_comments()` is soft-deprecated in favor of `use_comments()`. + It still works but emits a `.Deprecated()` warning and delegates. + +## New features + +- `clear_metadata_file()` — new exported function. Selectively clears + `based_on`, `copied_from`, and/or `tags` in a model's metadata file; + unspecified fields are preserved. +- `copy_model()` gains `based_on` and `tags` arguments so metadata can be + populated at copy time rather than via a follow-up `set_metadata_file()`. +- `set_metadata_file()` gains `copied_from` to record mechanical-copy + provenance separately from `based_on`. +- `hyperion.config_dir` option — explicit override for where `pharos.toml` is + resolved from. Status is surfaced in the package-load options message. +- `use_comments(type = c("type1", "type2"))` — single entry point for setting + comment parsing type in `pharos.toml`. The new `"type2"` mode is a flexible + structured grammar; `"type1"` remains strict structured. Replaces + `use_type1_comments()`. +- `summary()` includes `model_file` in its output. +- `get_model_lineage(verbose = TRUE)` renders the lineage as a flat table + with Model, Parent, Description, Tags, Model Hash, and Dataset Hash columns + instead of the tree view. + +## Bug fixes + +- `.ext` parameter columns are sanitized to syntactic R names (e.g. + `IIV (CL)` → `IIV_CL`), so returned data frames no longer require backtick + quoting. +- `.grd` gradient column names are similarly sanitized; the surrounding + `GRD(...)` wrapper is stripped before sanitization. +- Fixed `clear_metadata_file()` so previously-set values are actually cleared + (prior behavior left stale fields in place). +- `read_model()` reports parsing diagnostics for malformed NONMEM control + streams, backed by a new pharos parser aimed at better control-stream + coverage. +- `$EST FILE=` overrides are now honored when locating `.ext` files in + `get_parameters()`, `read_ext_file()`, `get_run_status()`, and + `copy_model()` parameter updates. Previously these always used + `{model}.ext` regardless of where NONMEM was actually writing estimates. + + # hyperion 0.4.2 ## Bug fixes diff --git a/R/comments-audit.R b/R/comments-audit.R index 102f259a..24eee3b2 100644 --- a/R/comments-audit.R +++ b/R/comments-audit.R @@ -44,7 +44,7 @@ audit_parameter_info <- function(info) { #' @param x A parameter_audit object #' @param ... Additional arguments (ignored) #' @return Invisible copy of x -#' @rawNamespace S3method(base::print, parameter_audit) +#' @exportS3Method base::print parameter_audit print.parameter_audit <- function(x, ...) { cli::cli_text("") cli::cli_h1("Parameter Info Audit") diff --git a/R/dataset.R b/R/dataset.R index fc45fe94..f32f18eb 100644 --- a/R/dataset.R +++ b/R/dataset.R @@ -3,7 +3,7 @@ #' @param x A hyperion_nonmem_dataset object #' @param ... Additional arguments (ignored) #' @return Invisible copy of x -#' @rawNamespace S3method(base::print, hyperion_nonmem_dataset) +#' @exportS3Method base::print hyperion_nonmem_dataset print.hyperion_nonmem_dataset <- function(x, ...) { rel_path <- to_config_relative(x$canonical_path) diff --git a/R/metadata-methods.R b/R/metadata-methods.R index cd37dc57..4fe875fb 100644 --- a/R/metadata-methods.R +++ b/R/metadata-methods.R @@ -3,7 +3,7 @@ #' @param x A hyperion_model_metadata object #' @param ... Additional arguments (ignored) #' @return Invisible copy of x -#' @rawNamespace S3method(base::print, hyperion_model_metadata) +#' @exportS3Method base::print hyperion_model_metadata print.hyperion_model_metadata <- function(x, ...) { description <- x$description %||% "" tags <- x$tags %||% character(0) diff --git a/R/model-methods.R b/R/model-methods.R index 6c2bf075..62af308a 100644 --- a/R/model-methods.R +++ b/R/model-methods.R @@ -4,7 +4,7 @@ #' @param digits Number of significant digits (uses global option if NULL) #' @param ... Additional arguments (ignored) #' @return Invisible copy of x -#' @rawNamespace S3method(base::print, hyperion_nonmem_model) +#' @exportS3Method base::print hyperion_nonmem_model print.hyperion_nonmem_model <- function(x, digits = NULL, ...) { parts <- build_model_display_parts(x, digits) @@ -78,7 +78,7 @@ print.hyperion_nonmem_model <- function(x, digits = NULL, ...) { #' Default is 10. #' @param ... Additional arguments (currently unused) #' @return A hyperion_nonmem_summary object -#' @rawNamespace S3method(base::summary, hyperion_nonmem_model) +#' @exportS3Method base::summary hyperion_nonmem_model summary.hyperion_nonmem_model <- function( object, hide_off_diagonal_params = FALSE, @@ -252,7 +252,7 @@ validate_n_iterations <- function(n_iterations) { #' @param object A hyperion_nonmem_model object #' @param ... Additional arguments passed to str #' @return Invisible NULL (called for side effects) -#' @rawNamespace S3method(utils::str, hyperion_nonmem_model) +#' @exportS3Method utils::str hyperion_nonmem_model str.hyperion_nonmem_model <- function(object, ...) { class(object) <- "list" object$source <- NULL @@ -266,7 +266,7 @@ str.hyperion_nonmem_model <- function(object, ...) { #' @param x A hyperion_nonmem_model object #' @param name The element name to access #' @return The element value, or NULL for restricted fields -#' @rawNamespace S3method(base::`$`, hyperion_nonmem_model) +#' @exportS3Method base::`$` hyperion_nonmem_model `$.hyperion_nonmem_model` <- function(x, name) { if (name %in% c("source")) { return(NULL) @@ -274,7 +274,7 @@ str.hyperion_nonmem_model <- function(object, ...) { .subset2(x, name) } -#' @rawNamespace S3method(base::`[[`, hyperion_nonmem_model) +#' @exportS3Method base::`[[` hyperion_nonmem_model `[[.hyperion_nonmem_model` <- function(x, i, ...) { if (is.character(i) && i %in% c("source")) { return(NULL) @@ -282,7 +282,7 @@ str.hyperion_nonmem_model <- function(object, ...) { NextMethod("[[") } -#' @rawNamespace S3method(base::names, hyperion_nonmem_model) +#' @exportS3Method base::names hyperion_nonmem_model names.hyperion_nonmem_model <- function(x) { n <- NextMethod("names") setdiff(n, c("source")) diff --git a/R/summary-methods.R b/R/summary-methods.R index 7c5c2bf3..c29597a5 100644 --- a/R/summary-methods.R +++ b/R/summary-methods.R @@ -334,7 +334,7 @@ print_not_run_summary <- function(x) { #' @param digits Number of significant digits (uses global option if NULL) #' @param ... Additional arguments (ignored) #' @return Invisible copy of x -#' @rawNamespace S3method(base::print, hyperion_nonmem_summary_running) +#' @exportS3Method base::print hyperion_nonmem_summary_running print.hyperion_nonmem_summary_running <- function(x, digits = NULL, ...) { print_running_summary(x, digits) invisible(x) @@ -345,7 +345,7 @@ print.hyperion_nonmem_summary_running <- function(x, digits = NULL, ...) { #' @param x A hyperion_nonmem_summary_not_run object #' @param ... Additional arguments (ignored) #' @return Invisible copy of x -#' @rawNamespace S3method(base::print, hyperion_nonmem_summary_not_run) +#' @exportS3Method base::print hyperion_nonmem_summary_not_run print.hyperion_nonmem_summary_not_run <- function(x, ...) { print_not_run_summary(x) invisible(x) @@ -357,7 +357,7 @@ print.hyperion_nonmem_summary_not_run <- function(x, ...) { #' @param digits Number of significant digits (uses global option if NULL) #' @param ... Additional arguments (ignored) #' @return Invisible copy of x -#' @rawNamespace S3method(base::print, hyperion_nonmem_summary) +#' @exportS3Method base::print hyperion_nonmem_summary print.hyperion_nonmem_summary <- function(x, digits = NULL, ...) { parts <- build_summary_display_parts(x, digits) diff --git a/R/tree-methods.R b/R/tree-methods.R index a5262a62..249d2811 100644 --- a/R/tree-methods.R +++ b/R/tree-methods.R @@ -37,14 +37,22 @@ build_tree_display_parts <- function(x) { #' Print Method for Hyperion Tree Objects #' #' Displays a hyperion_nonmem_tree in a readable tree format using cli::tree(). -#' Shows the hierarchical relationships between models with Unicode tree characters. +#' Shows the hierarchical relationships between models with Unicode tree +#' characters. The default compact view shows only the model description; pass +#' `verbose = TRUE` for a flat table that also includes tags. #' #' @param x A hyperion_nonmem_tree object +#' @param verbose Logical; if `TRUE`, render a flat table with model, +#' description, and tags columns instead of the tree view. #' @param ... Additional arguments (currently unused) #' #' @return Invisibly returns the input object -#' @rawNamespace S3method(base::print, hyperion_nonmem_tree) -print.hyperion_nonmem_tree <- function(x, ...) { +#' @exportS3Method base::print hyperion_nonmem_tree +print.hyperion_nonmem_tree <- function( + x, + verbose = isTRUE(attr(x, "verbose")), + ... +) { cli::cli_text("") parts <- build_tree_display_parts(x) @@ -58,6 +66,25 @@ print.hyperion_nonmem_tree <- function(x, ...) { cli::cli_alert_info("Models: {parts$total_models}") cli::cli_text("") + if (verbose) { + print_tree_table(parts) + } else { + print_tree_compact(parts) + } + + invisible(x) +} + +# Look up a node's model record by stripped name. +tree_node_model <- function(parts, node_name) { + mod_key <- paste0(node_name, ".mod") + ctl_key <- paste0(node_name, ".ctl") + node_key <- if (mod_key %in% names(parts$nodes)) mod_key else ctl_key + parts$nodes[[node_key]]$model +} + +print_tree_compact <- function(parts) { + width <- cli::console_width() final_output <- character() for (root_idx in seq_along(parts$root_nodes)) { @@ -67,9 +94,6 @@ print.hyperion_nonmem_tree <- function(x, ...) { for (i in seq_along(tree_output)) { line <- tree_output[i] node_name <- gsub("^[^a-zA-Z0-9._]*", "", line) - mod_key <- paste0(node_name, ".mod") - ctl_key <- paste0(node_name, ".ctl") - node_key <- if (mod_key %in% names(parts$nodes)) mod_key else ctl_key is_root <- (node_name %in% parts$root_nodes) children <- parts$tree_data$children[ @@ -92,25 +116,19 @@ print.hyperion_nonmem_tree <- function(x, ...) { cli::col_yellow(display_name) } - node_model <- parts$nodes[[node_key]]$model - has_tags <- !is.null(node_model) && length(node_model$tags) > 0 + node_model <- tree_node_model(parts, node_name) has_desc <- !is.null(node_model) && !is.null(node_model$description) && nzchar(node_model$description) suffix <- "" - if (has_tags) { - suffix <- paste0( - " ", - cli::col_cyan(paste(node_model$tags, collapse = ", ")) - ) - } if (has_desc) { + used <- cli::ansi_nchar(tree_prefix) + nchar(node_name) + 1 + budget <- max(30, width - used - 1) desc_text <- node_model$description - if (nchar(desc_text) > 50) { - desc_text <- paste0(substr(desc_text, 1, 47), "...") + if (nchar(desc_text) > budget) { + desc_text <- paste0(substr(desc_text, 1, budget - 3), "...") } - sep <- if (has_tags) cli::style_dim(" | ") else " " - suffix <- paste0(suffix, sep, cli::style_dim(desc_text)) + suffix <- paste0(" ", cli::style_dim(desc_text)) } final_output <- c(final_output, paste0(tree_prefix, colored_node, suffix)) } @@ -121,7 +139,138 @@ print.hyperion_nonmem_tree <- function(x, ...) { } cat(final_output, sep = "\n") - invisible(x) +} + +format_hash <- function(h) { + if (is.null(h) || !is.character(h) || !nzchar(h)) { + return("") + } + if (nchar(h) > 8) paste0(substr(h, 1, 8), "...") else h +} + +# Full node record (not just $model) for hash lookups. +tree_node_record <- function(parts, node_name) { + mod_key <- paste0(node_name, ".mod") + ctl_key <- paste0(node_name, ".ctl") + node_key <- if (mod_key %in% names(parts$nodes)) mod_key else ctl_key + parts$nodes[[node_key]] +} + +# Hard upper bounds for content-sized columns; columns shrink to fit content +# when the longest value is shorter. +MAX_DESC_WIDTH <- 60 +MAX_TAGS_WIDTH <- 80 + +print_tree_table <- function(parts) { + # Collect rows in DFS (tree) order so the table reads top-to-bottom like the + # tree view. Parent column preserves lineage info that the flat layout loses. + rows <- list() + for (root_node in parts$root_nodes) { + tree_output <- cli::tree(parts$tree_data, root = root_node) + for (line in tree_output) { + node_name <- gsub("^[^a-zA-Z0-9._]*", "", line) + node <- tree_node_record(parts, node_name) + node_model <- node$model + node_run <- node$run + + parent <- if ( + !is.null(node_model$based_on) && + length(node_model$based_on) > 0 + ) { + gsub("\\.(mod|ctl)$", "", node_model$based_on[[1]]) + } else { + "" + } + desc <- if (!is.null(node_model$description)) { + node_model$description + } else { + "" + } + tags <- if (length(node_model$tags) > 0) { + paste(node_model$tags, collapse = ", ") + } else { + "" + } + model_hash <- format_hash(node_run$start$model_hashes$blake3) + dataset_hash <- format_hash(node_run$start$dataset_hashes$blake3) + + rows[[length(rows) + 1]] <- list( + model = node_name, + parent = parent, + description = desc, + tags = tags, + model_hash = model_hash, + dataset_hash = dataset_hash + ) + } + } + + col_w <- function(col, label) { + max(c(nchar(label), vapply(rows, function(r) nchar(r[[col]]), integer(1)))) + } + model_w <- col_w("model", "Model") + parent_w <- col_w("parent", "Parent") + desc_w <- min(col_w("description", "Description"), MAX_DESC_WIDTH) + tags_w <- min(col_w("tags", "Tags"), MAX_TAGS_WIDTH) + model_hash_w <- col_w("model_hash", "Model Hash") + dataset_hash_w <- col_w("dataset_hash", "Dataset Hash") + + truncate <- function(s, w) { + if (nchar(s) > w) paste0(substr(s, 1, w - 3), "...") else s + } + fmt_row <- function(model, parent, desc, tags, mh, dh) { + sprintf( + "%-*s %-*s %-*s %-*s %-*s %-*s", + model_w, + model, + parent_w, + parent, + desc_w, + truncate(desc, desc_w), + tags_w, + truncate(tags, tags_w), + model_hash_w, + mh, + dataset_hash_w, + dh + ) + } + + # 2 spaces between 6 cols = 10 separator chars. + total_w <- model_w + + parent_w + + desc_w + + tags_w + + model_hash_w + + dataset_hash_w + + 10 + cat( + cli::style_bold(fmt_row( + "Model", + "Parent", + "Description", + "Tags", + "Model Hash", + "Dataset Hash" + )), + "\n", + sep = "" + ) + cat(strrep("\u2500", total_w), "\n", sep = "") + for (r in rows) { + cat( + fmt_row( + r$model, + r$parent, + r$description, + r$tags, + r$model_hash, + r$dataset_hash + ), + "\n", + sep = "" + ) + } } #' Build Tree Data for cli::tree() @@ -175,7 +324,9 @@ build_cli_tree_data <- function(nodes_by_name) { } #' Knit print method for hyperion_nonmem_tree objects (for Quarto/R Markdown) -#' @param x A hyperion_nonmem_tree object +#' @param x A hyperion_nonmem_tree object. If the object carries a `"verbose"` +#' attribute (set by `get_model_lineage(verbose = TRUE)`), the flat table +#' layout is rendered instead of the tree view. #' @param ... Additional arguments (ignored) #' @return HTML/markdown output for rendered documents #' @exportS3Method knitr::knit_print @@ -206,25 +357,88 @@ knit_print.hyperion_nonmem_tree <- function(x, ...) { "" ) - for (root_idx in seq_along(parts$root_nodes)) { - root_node <- parts$root_nodes[root_idx] - tree_lines <- knit_print_tree_node( - root_node, - parts$tree_data, - parts$nodes, - parts$focal_display, - level = 0 - ) - output <- c(output, tree_lines) + if (isTRUE(attr(x, "verbose"))) { + output <- c(output, knit_print_tree_table(parts)) + } else { + for (root_idx in seq_along(parts$root_nodes)) { + root_node <- parts$root_nodes[root_idx] + tree_lines <- knit_print_tree_node( + root_node, + parts$tree_data, + parts$nodes, + parts$focal_display, + level = 0 + ) + output <- c(output, tree_lines) - if (root_idx < length(parts$root_nodes)) { - output <- c(output, "") + if (root_idx < length(parts$root_nodes)) { + output <- c(output, "") + } } } knitr::asis_output(paste(output, collapse = "\n")) } +# Markdown table renderer for knit_print verbose mode. Mirrors the columns in +# print_tree_table so the two views are consistent. +knit_print_tree_table <- function(parts) { + rows <- list() + for (root_node in parts$root_nodes) { + tree_output <- cli::tree(parts$tree_data, root = root_node) + for (line in tree_output) { + node_name <- gsub("^[^a-zA-Z0-9._]*", "", line) + node <- tree_node_record(parts, node_name) + node_model <- node$model + node_run <- node$run + + parent <- if ( + !is.null(node_model$based_on) && + length(node_model$based_on) > 0 + ) { + gsub("\\.(mod|ctl)$", "", node_model$based_on[[1]]) + } else { + "" + } + desc <- if (!is.null(node_model$description)) { + node_model$description + } else { + "" + } + tags <- if (length(node_model$tags) > 0) { + paste(node_model$tags, collapse = ", ") + } else { + "" + } + model_hash <- format_hash(node_run$start$model_hashes$blake3) + dataset_hash <- format_hash(node_run$start$dataset_hashes$blake3) + + rows[[length(rows) + 1]] <- c( + node_name, + parent, + desc, + tags, + model_hash, + dataset_hash + ) + } + } + + header <- c( + "| Model | Parent | Description | Tags | Model Hash | Dataset Hash |", + "|---|---|---|---|---|---|" + ) + body <- vapply( + rows, + function(r) { + paste0("| ", paste(r, collapse = " | "), " |") + }, + character(1) + ) + + c(header, body) +} + #' Helper function to recursively build tree structure in markdown #' @param node_name Current node name #' @param tree_data Tree data structure from build_cli_tree_data @@ -380,3 +594,24 @@ are_models_in_lineage <- function(m1, m2) { length(get_model_lineage(from = m1, to = m2)$nodes) > 0 || length(get_model_lineage(from = m2, to = m1)$nodes) > 0 } + +# Override the extendr-generated `get_model_lineage` to accept `verbose` and +# stash it on the returned tree as an attribute. Both `print()` and +# `knit_print()` read this attribute, so `get_model_lineage(verbose = TRUE)` +# at the REPL or in a knitr chunk renders the flat-table view without the +# caller having to remember the print-time flag. +#' +#' @rdname get_model_lineage +#' @param verbose Logical; if `TRUE`, the returned tree carries a +#' `"verbose"` attribute that causes `print()` and `knit_print()` to render +#' a flat 6-column table (Model, Parent, Description, Tags, Model Hash, +#' Dataset Hash) instead of the tree view. +#' @export +get_model_lineage <- (function() { + raw <- get_model_lineage + function(model = NULL, from = NULL, to = NULL, verbose = FALSE) { + tree <- raw(model = model, from = from, to = to) + attr(tree, "verbose") <- isTRUE(verbose) + tree + } +})() diff --git a/man/get_model_lineage.Rd b/man/get_model_lineage.Rd index 6b83490f..ae653420 100644 --- a/man/get_model_lineage.Rd +++ b/man/get_model_lineage.Rd @@ -1,10 +1,12 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/extendr-wrappers.R +% Please edit documentation in R/extendr-wrappers.R, R/tree-methods.R \name{get_model_lineage} \alias{get_model_lineage} \title{Show model lineage and relationships.} \usage{ -get_model_lineage(model = NULL, from = NULL, to = NULL) +get_model_lineage(model = NULL, from = NULL, to = NULL, verbose = FALSE) + +get_model_lineage(model = NULL, from = NULL, to = NULL, verbose = FALSE) } \arguments{ \item{model}{Optional \code{hyperion_nonmem_model} object or model file path. @@ -16,6 +18,11 @@ Accepts a \code{hyperion_nonmem_model} object or a model file path.} \item{to}{Filter the tree to this model and everything upstream. Accepts a \code{hyperion_nonmem_model} object or a model file path.} + +\item{verbose}{Logical; if \code{TRUE}, the returned tree carries a +\code{"verbose"} attribute that causes \code{print()} and \code{knit_print()} to render +a flat 6-column table (Model, Parent, Description, Tags, Model Hash, +Dataset Hash) instead of the tree view.} } \value{ hyperion_nonmem_tree S3 object diff --git a/man/hyperion-package.Rd b/man/hyperion-package.Rd index 238b4850..ea90f8a6 100644 --- a/man/hyperion-package.Rd +++ b/man/hyperion-package.Rd @@ -141,6 +141,7 @@ Useful links: Authors: \itemize{ + \item Matt Smith \email{matthews@a2-ai.com} \item Vincent Prouillet \email{vincent@a2-ai.com} \item Mossa M. Reimert \email{mossa@a2-ai.com} \item Devin Pastoor \email{devin@a2-ai.com} diff --git a/man/knit_print.hyperion_nonmem_tree.Rd b/man/knit_print.hyperion_nonmem_tree.Rd index ae65b663..9e3ac94c 100644 --- a/man/knit_print.hyperion_nonmem_tree.Rd +++ b/man/knit_print.hyperion_nonmem_tree.Rd @@ -7,7 +7,9 @@ \method{knit_print}{hyperion_nonmem_tree}(x, ...) } \arguments{ -\item{x}{A hyperion_nonmem_tree object} +\item{x}{A hyperion_nonmem_tree object. If the object carries a \code{"verbose"} +attribute (set by \code{get_model_lineage(verbose = TRUE)}), the flat table +layout is rendered instead of the tree view.} \item{...}{Additional arguments (ignored)} } diff --git a/man/print.hyperion_nonmem_tree.Rd b/man/print.hyperion_nonmem_tree.Rd index 65febd7a..9eb4d0ff 100644 --- a/man/print.hyperion_nonmem_tree.Rd +++ b/man/print.hyperion_nonmem_tree.Rd @@ -4,11 +4,14 @@ \alias{print.hyperion_nonmem_tree} \title{Print Method for Hyperion Tree Objects} \usage{ -\method{print}{hyperion_nonmem_tree}(x, ...) +\method{print}{hyperion_nonmem_tree}(x, verbose = isTRUE(attr(x, "verbose")), ...) } \arguments{ \item{x}{A hyperion_nonmem_tree object} +\item{verbose}{Logical; if \code{TRUE}, render a flat table with model, +description, and tags columns instead of the tree view.} + \item{...}{Additional arguments (currently unused)} } \value{ @@ -16,5 +19,7 @@ Invisibly returns the input object } \description{ Displays a hyperion_nonmem_tree in a readable tree format using cli::tree(). -Shows the hierarchical relationships between models with Unicode tree characters. +Shows the hierarchical relationships between models with Unicode tree +characters. The default compact view shows only the model description; pass +\code{verbose = TRUE} for a flat table that also includes tags. } diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index b15a56e4..a4b7c75a 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -141,7 +141,7 @@ dependencies = [ [[package]] name = "config" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=copy-ext#4f942e66afc5f6d39fa5af416bf556f40c3945c3" +source = "git+https://github.com/a2-ai/pharos?branch=main#31a9cfb4e7700f22308f2e856797ea90008c07b6" dependencies = [ "anyhow", "fs-err", @@ -741,7 +741,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "nonmem" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=copy-ext#4f942e66afc5f6d39fa5af416bf556f40c3945c3" +source = "git+https://github.com/a2-ai/pharos?branch=main#31a9cfb4e7700f22308f2e856797ea90008c07b6" dependencies = [ "anyhow", "blake3", @@ -766,7 +766,7 @@ dependencies = [ [[package]] name = "nonmem-parser" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=copy-ext#4f942e66afc5f6d39fa5af416bf556f40c3945c3" +source = "git+https://github.com/a2-ai/pharos?branch=main#31a9cfb4e7700f22308f2e856797ea90008c07b6" dependencies = [ "anyhow", "distrs", @@ -1125,7 +1125,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=copy-ext#4f942e66afc5f6d39fa5af416bf556f40c3945c3" +source = "git+https://github.com/a2-ai/pharos?branch=main#31a9cfb4e7700f22308f2e856797ea90008c07b6" dependencies = [ "anyhow", "config", @@ -1377,7 +1377,7 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utils" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=copy-ext#4f942e66afc5f6d39fa5af416bf556f40c3945c3" +source = "git+https://github.com/a2-ai/pharos?branch=main#31a9cfb4e7700f22308f2e856797ea90008c07b6" dependencies = [ "anyhow", "fs-err", diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 5bc64f49..b2891988 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -38,10 +38,10 @@ serde = { workspace = true } extendr-api = { version = "0.9.0", features = ["serde"] } # Pharos components -nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "copy-ext" } -nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", branch = "copy-ext" } -config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "copy-ext" } -scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "copy-ext" } +nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "main" } +nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", branch = "main" } +config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "main" } +scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "main" } # Core utilities anyhow = "1.0.100" diff --git a/tests/testthat/_snaps/hyperion-tree-print.md b/tests/testthat/_snaps/hyperion-tree-print.md index bfe322ac..9ff5e414 100644 --- a/tests/testthat/_snaps/hyperion-tree-print.md +++ b/tests/testthat/_snaps/hyperion-tree-print.md @@ -13,3 +13,36 @@ \-run001 Run 1 \-run002 Run 2 with covariate effects +# hyperion_nonmem_tree print honors verbose attr + + Code + print(tree) + Message + + + -- Hyperion Model Tree --------------------------------------------------------- + i Models: 2 + + Output + Model Parent Description Tags Model Hash Dataset Hash + ───────────────────────────────────────────────────────────────────────── + base Base population PK model base f873a13c... 8d8189cf... + run001 base Adding COV step + +# hyperion_nonmem_tree verbose print renders 6-column table + + Code + print(tree, verbose = TRUE) + Message + + + -- Hyperion Model Tree --------------------------------------------------------- + i Models: 3 + + Output + Model Parent Description Tags Model Hash Dataset Hash + ──────────────────────────────────────────────────────────────────────────────────────────── + base Base population PK model base f873a13c... 8d8189cf... + run001 base Adding COV step, unfixing CL covariates, unfixed 1a0f07a1... 8d8189cf... + run002 run001 Not yet run + diff --git a/tests/testthat/_snaps/lineage.md b/tests/testthat/_snaps/lineage.md index 8b2732bc..337b8cef 100644 --- a/tests/testthat/_snaps/lineage.md +++ b/tests/testthat/_snaps/lineage.md @@ -9,15 +9,15 @@ i Models: 9 Output - extdata/models/onecmt/run001 base | Base model + extdata/models/onecmt/run001 Base model +-extdata/models/onecmt/run002 Adding COV step, unfixing eps(2) - | +-extdata/models/onecmt/run002a Some description about what makes run002a diffe... - | +-extdata/models/onecmt/run002b001 not run, 2cmt | Jittering initial sigma estimates, using theta/... - | \-extdata/models/onecmt/run003 key | Jittering initial estimates - | +-extdata/models/onecmt/run003b1 Updating run003 to 003b1 with jittered params. ... + | +-extdata/models/onecmt/run002a Some description about what makes run002a ... + | +-extdata/models/onecmt/run002b001 Jittering initial sigma estimates, usin... + | \-extdata/models/onecmt/run003 Jittering initial estimates + | +-extdata/models/onecmt/run003b1 Updating run003 to 003b1 with jittered ... | \-extdata/models/onecmt/run003b2 Updating run003 with mod object - +-extdata/models/onecmt/run004 Updating run001 to run004 with jittered params ... - \-extdata/models/onecmt/run005 Updating run001 to run004 with jittered params ... + +-extdata/models/onecmt/run004 Updating run001 to run004 with jittered param... + \-extdata/models/onecmt/run005 Updating run001 to run004 with jittered param... # get_model_lineage(model) returns the model's full lineage @@ -30,10 +30,10 @@ i Models: 5 Output - extdata/models/onecmt/run001 base | Base model + extdata/models/onecmt/run001 Base model \-extdata/models/onecmt/run002 Adding COV step, unfixing eps(2) - \-extdata/models/onecmt/run003 key | Jittering initial estimates - +-extdata/models/onecmt/run003b1 Updating run003 to 003b1 with jittered params. ... + \-extdata/models/onecmt/run003 Jittering initial estimates + +-extdata/models/onecmt/run003b1 Updating run003 to 003b1 with jittered ... \-extdata/models/onecmt/run003b2 Updating run003 with mod object # get_model_lineage(from, to) slices between two models @@ -47,10 +47,10 @@ i Models: 4 Output - extdata/models/onecmt/run001 base | Base model + extdata/models/onecmt/run001 Base model \-extdata/models/onecmt/run002 Adding COV step, unfixing eps(2) - \-extdata/models/onecmt/run003 key | Jittering initial estimates - \-extdata/models/onecmt/run003b1 Updating run003 to 003b1 with jittered params. ... + \-extdata/models/onecmt/run003 Jittering initial estimates + \-extdata/models/onecmt/run003b1 Updating run003 to 003b1 with jittered ... # lineage helpers return project-relative paths diff --git a/tests/testthat/test-hyperion-tree-print.R b/tests/testthat/test-hyperion-tree-print.R index a91d4d63..19dbebf6 100644 --- a/tests/testthat/test-hyperion-tree-print.R +++ b/tests/testthat/test-hyperion-tree-print.R @@ -35,3 +35,80 @@ test_that("hyperion_nonmem_tree print works", { ) expect_snapshot(print(tree)) }) + +test_that("hyperion_nonmem_tree print honors verbose attr", { + tree <- structure( + list( + nodes = list( + list( + name = "base.mod", + model = list( + based_on = list(), + description = "Base population PK model", + tags = list("base") + ), + run = list(start = list( + model_hashes = list(blake3 = "f873a13ca1b2c3d4e5f6"), + dataset_hashes = list(blake3 = "8d8189cfaabb11223344") + )) + ), + list( + name = "run001.mod", + model = list( + based_on = list("base.mod"), + description = "Adding COV step", + tags = list() + ), + run = NULL + ) + ) + ), + class = "hyperion_nonmem_tree" + ) + attr(tree, "verbose") <- TRUE + expect_snapshot(print(tree)) +}) + +test_that("hyperion_nonmem_tree verbose print renders 6-column table", { + tree <- structure( + list( + nodes = list( + list( + name = "base.mod", + model = list( + based_on = list(), + description = "Base population PK model", + tags = list("base") + ), + run = list(start = list( + model_hashes = list(blake3 = "f873a13ca1b2c3d4e5f6"), + dataset_hashes = list(blake3 = "8d8189cfaabb11223344") + )) + ), + list( + name = "run001.mod", + model = list( + based_on = list("base.mod"), + description = "Adding COV step, unfixing CL", + tags = list("covariates", "unfixed") + ), + run = list(start = list( + model_hashes = list(blake3 = "1a0f07a1112233445566"), + dataset_hashes = list(blake3 = "8d8189cfaabb11223344") + )) + ), + list( + name = "run002.mod", + model = list( + based_on = list("run001.mod"), + description = "Not yet run", + tags = list() + ), + run = NULL + ) + ) + ), + class = "hyperion_nonmem_tree" + ) + expect_snapshot(print(tree, verbose = TRUE)) +}) From 266e92667f6782804987a2db16a8aa28d9d3e6fa Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Thu, 14 May 2026 12:03:12 -0400 Subject: [PATCH 34/41] update DESCRIPTION and pharos tag --- DESCRIPTION | 2 +- src/rust/Cargo.lock | 10 +++++----- src/rust/Cargo.toml | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 73127066..f1d41531 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: hyperion Title: Pharmaceutical Model Development and Workflow Tools -Version: 0.4.2 +Version: 0.5.0 Authors@R: c( person("Matt", "Smith", , "matthews@a2-ai.com", role = c("aut", "cre")), person("Vincent", "Prouillet", ,"vincent@a2-ai.com", role = c("aut")), diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index a4b7c75a..4dc102e9 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -141,7 +141,7 @@ dependencies = [ [[package]] name = "config" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=main#31a9cfb4e7700f22308f2e856797ea90008c07b6" +source = "git+https://github.com/a2-ai/pharos?tag=v0.5.0#8a55a9a2babd81105617b8a1e178b48847a03d9f" dependencies = [ "anyhow", "fs-err", @@ -741,7 +741,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "nonmem" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=main#31a9cfb4e7700f22308f2e856797ea90008c07b6" +source = "git+https://github.com/a2-ai/pharos?tag=v0.5.0#8a55a9a2babd81105617b8a1e178b48847a03d9f" dependencies = [ "anyhow", "blake3", @@ -766,7 +766,7 @@ dependencies = [ [[package]] name = "nonmem-parser" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=main#31a9cfb4e7700f22308f2e856797ea90008c07b6" +source = "git+https://github.com/a2-ai/pharos?tag=v0.5.0#8a55a9a2babd81105617b8a1e178b48847a03d9f" dependencies = [ "anyhow", "distrs", @@ -1125,7 +1125,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=main#31a9cfb4e7700f22308f2e856797ea90008c07b6" +source = "git+https://github.com/a2-ai/pharos?tag=v0.5.0#8a55a9a2babd81105617b8a1e178b48847a03d9f" dependencies = [ "anyhow", "config", @@ -1377,7 +1377,7 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utils" version = "0.1.0" -source = "git+https://github.com/a2-ai/pharos?branch=main#31a9cfb4e7700f22308f2e856797ea90008c07b6" +source = "git+https://github.com/a2-ai/pharos?tag=v0.5.0#8a55a9a2babd81105617b8a1e178b48847a03d9f" dependencies = [ "anyhow", "fs-err", diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index b2891988..334bd8f9 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -38,10 +38,10 @@ serde = { workspace = true } extendr-api = { version = "0.9.0", features = ["serde"] } # Pharos components -nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", branch = "main" } -nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", branch = "main" } -config = { git = "https://github.com/a2-ai/pharos", package = "config", branch = "main" } -scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", branch = "main" } +nonmem = { git = "https://github.com/a2-ai/pharos", package = "nonmem", tag = "v0.5.0" } +nmparser = { git = "https://github.com/a2-ai/pharos", package = "nonmem-parser", tag = "v0.5.0" } +config = { git = "https://github.com/a2-ai/pharos", package = "config", tag = "v0.5.0" } +scheduler = { git = "https://github.com/a2-ai/pharos", package = "scheduler", tag = "v0.5.0" } # Core utilities anyhow = "1.0.100" From a96bade04dd035ecd18e5513e6008d64024e0780 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Thu, 14 May 2026 12:16:07 -0400 Subject: [PATCH 35/41] update dep check to work with tags --- .../workflows/pharos-dependency-check.yaml | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pharos-dependency-check.yaml b/.github/workflows/pharos-dependency-check.yaml index d6176233..e4095c24 100644 --- a/.github/workflows/pharos-dependency-check.yaml +++ b/.github/workflows/pharos-dependency-check.yaml @@ -15,14 +15,29 @@ jobs: - name: Check pharos dependency status run: | - # Get pharos branch from Cargo.toml + # Determine whether Cargo.toml pins pharos via branch or tag. + # Branch pin: compare lock commit to that branch's HEAD. + # Tag pin: compare lock commit to main's HEAD (so we still get + # notified when pharos advances past a release pin). BRANCH=$(grep -A 2 'git = "https://github.com/a2-ai/pharos"' src/rust/Cargo.toml | grep 'branch =' | sed 's/.*branch = "\([^"]*\)".*/\1/' | head -1) + TAG=$(grep -A 2 'git = "https://github.com/a2-ai/pharos"' src/rust/Cargo.toml | grep 'tag =' | sed 's/.*tag = "\([^"]*\)".*/\1/' | head -1) - # Get current and latest commits CURRENT=$(grep -A 1 'source = "git+https://github.com/a2-ai/pharos' src/rust/Cargo.lock | grep -o '#[a-f0-9]\{40\}' | head -1 | cut -c2-) - LATEST=$(git ls-remote https://github.com/a2-ai/pharos refs/heads/$BRANCH | cut -f1) - echo "Branch: $BRANCH" + if [ -n "$BRANCH" ]; then + TARGET_REF="refs/heads/$BRANCH" + TARGET_LABEL="branch $BRANCH" + elif [ -n "$TAG" ]; then + TARGET_REF="refs/heads/main" + TARGET_LABEL="main (pinned to tag $TAG)" + else + echo "::error::Could not determine pharos pin style (branch or tag) in Cargo.toml" + exit 1 + fi + + LATEST=$(git ls-remote https://github.com/a2-ai/pharos $TARGET_REF | cut -f1) + + echo "Pin: $TARGET_LABEL" echo "Current: $CURRENT" echo "Latest: $LATEST" From 4c0e0b85edc77426219c1980e6617fc579bef9a1 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Fri, 15 May 2026 09:06:35 -0400 Subject: [PATCH 36/41] cleanup ParsedXComment -> XCommentInfo conversioon --- src/rust/nonmem/src/model/comment_info.rs | 150 +++++++++++----------- 1 file changed, 76 insertions(+), 74 deletions(-) diff --git a/src/rust/nonmem/src/model/comment_info.rs b/src/rust/nonmem/src/model/comment_info.rs index 38c0ce2f..dced364e 100644 --- a/src/rust/nonmem/src/model/comment_info.rs +++ b/src/rust/nonmem/src/model/comment_info.rs @@ -14,14 +14,48 @@ use crate::model::parameters::compare_param_names; use crate::model::robj_to_model; use hyperion_core::ResultExt; -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Default, Serialize)] pub struct ThetaCommentInfo { pub name: Option, pub unit: Option, pub parameterization: Option, } -#[derive(Debug, Clone, Serialize)] +impl From<&ParsedThetaComment> for ThetaCommentInfo { + fn from(parsed: &ParsedThetaComment) -> Self { + match parsed { + ParsedThetaComment::Type1(Type1Theta::WithUnit { + parameter, + unit, + parametrization, + }) => ThetaCommentInfo { + name: Some(parameter.clone()), + unit: Some(unit.clone()), + parameterization: parametrization.as_deref().and_then(map_parameterization), + }, + ParsedThetaComment::Type1(Type1Theta::Covariate { parameter }) => ThetaCommentInfo { + name: Some(parameter.clone()), + unit: None, + parameterization: None, + }, + ParsedThetaComment::Type1(Type1Theta::Type { + typ, + parameterization, + }) => ThetaCommentInfo { + name: Some(typ.clone()), + unit: None, + parameterization: map_parameterization(parameterization), + }, + ParsedThetaComment::Type2(t) => ThetaCommentInfo { + name: Some(t.name.clone()), + unit: t.unit.clone(), + parameterization: t.parameterization.as_ref().map(ToString::to_string), + }, + } + } +} + +#[derive(Debug, Clone, Default, Serialize)] pub struct OmegaCommentInfo { pub name: Option, pub raw_name: Option, @@ -29,13 +63,49 @@ pub struct OmegaCommentInfo { pub parameterization: Option, } -#[derive(Debug, Clone, Serialize)] +impl From<&ParsedOmegaComment> for OmegaCommentInfo { + fn from(parsed: &ParsedOmegaComment) -> Self { + match parsed { + ParsedOmegaComment::Type1(o) => OmegaCommentInfo { + name: parsed.name(), + raw_name: Some(o.name.clone()), + associated_theta: vec![o.theta_name.clone()], + parameterization: map_parameterization(&o.parameterization), + }, + ParsedOmegaComment::Type2(o) => OmegaCommentInfo { + name: parsed.name(), + raw_name: Some(o.name.clone()), + associated_theta: o.raw_theta_refs.clone(), + parameterization: o.parameterization.as_ref().map(ToString::to_string), + }, + } + } +} + +#[derive(Debug, Clone, Default, Serialize)] pub struct SigmaCommentInfo { pub name: Option, pub unit: Option, pub parameterization: Option, } +impl From<&ParsedSigmaComment> for SigmaCommentInfo { + fn from(parsed: &ParsedSigmaComment) -> Self { + match parsed { + ParsedSigmaComment::Type1(s) => SigmaCommentInfo { + name: Some(s.name.clone()), + unit: None, + parameterization: s.parameterization.as_deref().and_then(map_parameterization), + }, + ParsedSigmaComment::Type2(s) => SigmaCommentInfo { + name: Some(s.name.clone()), + unit: s.unit.clone(), + parameterization: s.parameterization.as_ref().map(ToString::to_string), + }, + } + } +} + #[derive(Debug, Clone, Serialize)] pub struct ModelCommentInfo { pub thetas: Vec<(String, ThetaCommentInfo)>, @@ -44,40 +114,7 @@ pub struct ModelCommentInfo { } fn build_theta_info(parsed: Option<&ParsedThetaComment>) -> ThetaCommentInfo { - match parsed { - Some(ParsedThetaComment::Type1(Type1Theta::WithUnit { - parameter, - unit, - parametrization, - })) => ThetaCommentInfo { - name: Some(parameter.clone()), - unit: Some(unit.clone()), - parameterization: parametrization.as_deref().and_then(map_parameterization), - }, - Some(ParsedThetaComment::Type1(Type1Theta::Covariate { parameter })) => ThetaCommentInfo { - name: Some(parameter.clone()), - unit: None, - parameterization: None, - }, - Some(ParsedThetaComment::Type1(Type1Theta::Type { - typ, - parameterization, - })) => ThetaCommentInfo { - name: Some(typ.clone()), - unit: None, - parameterization: map_parameterization(parameterization), - }, - Some(ParsedThetaComment::Type2(t)) => ThetaCommentInfo { - name: Some(t.name.clone()), - unit: t.unit.clone(), - parameterization: t.parameterization.as_ref().map(ToString::to_string), - }, - None => ThetaCommentInfo { - name: None, - unit: None, - parameterization: None, - }, - } + parsed.map(ThetaCommentInfo::from).unwrap_or_default() } fn build_omega_info(param: &OmegaSigmaParam) -> OmegaCommentInfo { @@ -89,26 +126,7 @@ fn build_omega_info(param: &OmegaSigmaParam) -> OmegaCommentInfo { None => None, }; - match inner { - Some(parsed @ ParsedOmegaComment::Type1(o)) => OmegaCommentInfo { - name: parsed.name(), - raw_name: Some(o.name.clone()), - associated_theta: vec![o.theta_name.clone()], - parameterization: map_parameterization(&o.parameterization), - }, - Some(parsed @ ParsedOmegaComment::Type2(o)) => OmegaCommentInfo { - name: parsed.name(), - raw_name: Some(o.name.clone()), - associated_theta: o.raw_theta_refs.clone(), - parameterization: o.parameterization.as_ref().map(ToString::to_string), - }, - None => OmegaCommentInfo { - name: None, - raw_name: None, - associated_theta: Vec::new(), - parameterization: None, - }, - } + inner.map(OmegaCommentInfo::from).unwrap_or_default() } fn build_sigma_info(param: &OmegaSigmaParam) -> SigmaCommentInfo { @@ -120,23 +138,7 @@ fn build_sigma_info(param: &OmegaSigmaParam) -> SigmaCommentInfo { None => None, }; - match inner { - Some(ParsedSigmaComment::Type1(s)) => SigmaCommentInfo { - name: Some(s.name.clone()), - unit: None, - parameterization: s.parameterization.as_deref().and_then(map_parameterization), - }, - Some(ParsedSigmaComment::Type2(s)) => SigmaCommentInfo { - name: Some(s.name.clone()), - unit: s.unit.clone(), - parameterization: s.parameterization.as_ref().map(ToString::to_string), - }, - None => SigmaCommentInfo { - name: None, - unit: None, - parameterization: None, - }, - } + inner.map(SigmaCommentInfo::from).unwrap_or_default() } fn sort_entries(entries: BTreeMap) -> Vec<(String, T)> { From 2c08e31f574dbc7bd766dd4bc68c970adbe19cb8 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Fri, 15 May 2026 09:12:23 -0400 Subject: [PATCH 37/41] merge conflict spacing fix --- src/rust/core/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rust/core/src/lib.rs b/src/rust/core/src/lib.rs index 94c11798..e9b3a506 100644 --- a/src/rust/core/src/lib.rs +++ b/src/rust/core/src/lib.rs @@ -78,4 +78,4 @@ extendr_module! { fn silence_panic_output; fn find_pharos_config_file; -} \ No newline at end of file +} From 6c7a10b4fefdc27d4fd0192e803f9c01def29269 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Fri, 15 May 2026 09:23:04 -0400 Subject: [PATCH 38/41] remove robj_to_model --- src/rust/nonmem/src/model/check.rs | 4 ++-- src/rust/nonmem/src/model/comment_info.rs | 5 +++-- src/rust/nonmem/src/model/mod.rs | 6 ------ src/rust/nonmem/src/model/parameters.rs | 4 ++-- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/rust/nonmem/src/model/check.rs b/src/rust/nonmem/src/model/check.rs index bf5bd97c..553ebcbd 100644 --- a/src/rust/nonmem/src/model/check.rs +++ b/src/rust/nonmem/src/model/check.rs @@ -6,7 +6,7 @@ use hyperion_core::{OptionExt, ResultExt}; // pharos config and nonmem crates use nonmem::{check_dataset as nonmem_check_dataset, check_model}; -use crate::model::robj_to_model; +use extendr_api::deserializer::from_robj; use crate::utils::{load_nonmem_config, path_from_robj}; use hyperion_core::extendr_err; @@ -61,7 +61,7 @@ pub fn check_dataset(model: Robj) -> Result { .parent() .ok_or_extendr_err("Could not determine model directory")?; - let model = robj_to_model(&model)?; + let model = from_robj(&model)?; let dataset = nonmem_check_dataset(&model, model_dir).map_to_extendr_err("")?; let mut robj = to_robj(&dataset).map_to_extendr_err("Failed to serialize to Robj")?; diff --git a/src/rust/nonmem/src/model/comment_info.rs b/src/rust/nonmem/src/model/comment_info.rs index dced364e..4a5cd818 100644 --- a/src/rust/nonmem/src/model/comment_info.rs +++ b/src/rust/nonmem/src/model/comment_info.rs @@ -1,8 +1,10 @@ use std::collections::BTreeMap; use extendr_api::Result; +use extendr_api::deserializer::from_robj; use extendr_api::prelude::*; use extendr_api::serializer::to_robj; + use serde::Serialize; use nmparser::{ @@ -11,7 +13,6 @@ use nmparser::{ }; use crate::model::parameters::compare_param_names; -use crate::model::robj_to_model; use hyperion_core::ResultExt; #[derive(Debug, Clone, Default, Serialize)] @@ -187,7 +188,7 @@ pub fn build(model: &Model) -> anyhow::Result { /// @keywords internal #[extendr] pub fn get_model_comment_info(model: Robj) -> Result { - let model = robj_to_model(&model)?; + let model = from_robj(&model)?; let info = build(&model).map_to_extendr_err("Failed to build comment info")?; to_robj(&info).map_to_extendr_err("Failed to serialize comment info") } diff --git a/src/rust/nonmem/src/model/mod.rs b/src/rust/nonmem/src/model/mod.rs index 76e45d69..2ef8814b 100644 --- a/src/rust/nonmem/src/model/mod.rs +++ b/src/rust/nonmem/src/model/mod.rs @@ -1,5 +1,4 @@ use extendr_api::Result; -use extendr_api::deserializer::from_robj; use extendr_api::prelude::*; use extendr_api::serializer::to_robj; use fs_err as fs; @@ -36,11 +35,6 @@ pub fn model_to_robj(model: &mut Model, path: impl AsRef) -> Result set_model_class(&mut model_robj) } -/// Reconstruct a parser Model from a hyperion_nonmem_model Robj. -pub fn robj_to_model(model: &Robj) -> Result { - from_robj(model).map_to_extendr_err("Failed to create Model from Robj") -} - fn add_filename_attr(model_robj: &mut Robj, path: &Path) -> Result<()> { if let Some(n) = path.file_stem().and_then(|name| name.to_str()) { model_robj diff --git a/src/rust/nonmem/src/model/parameters.rs b/src/rust/nonmem/src/model/parameters.rs index 05684c87..159757bb 100644 --- a/src/rust/nonmem/src/model/parameters.rs +++ b/src/rust/nonmem/src/model/parameters.rs @@ -1,4 +1,5 @@ use extendr_api::Result; +use extendr_api::deserializer::from_robj; use extendr_api::prelude::*; use fs_err as fs; @@ -13,7 +14,6 @@ use nonmem::{ }; use crate::{ - model::robj_to_model, output_files::ext::create_ext_reader, output_files::{OMEGA, ParameterRow, ParameterRowBuilder, SIGMA, THETA, build_parameters_df}, utils::{find_output_file, get_comment_type, path_from_robj, resolve_ext_path}, @@ -254,7 +254,7 @@ pub fn get_parameters( /// @keywords internal #[extendr] pub fn get_model_parameter_names(model: Robj) -> Result { - let model = robj_to_model(&model)?; + let model: Model = from_robj(&model)?; let comment_type = get_comment_type(); let parameter_names = model From f185bb86d5c293129d193354c5519f6eb51fd7b7 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Fri, 15 May 2026 11:48:02 -0400 Subject: [PATCH 39/41] fix run status --- R/comments-utils.R | 12 +- inst/extdata/models/onecmt/run004/run004.lst | 5 +- src/rust/nonmem/src/model/mod.rs | 16 ++- src/rust/nonmem/src/model/run_status.rs | 132 ++++++------------ .../test_data/run001-running/pharos_end.json | 48 ------- .../test_data/run001-running/run001.lst | 3 - 6 files changed, 62 insertions(+), 154 deletions(-) delete mode 100644 src/rust/nonmem/test_data/run001-running/pharos_end.json diff --git a/R/comments-utils.R b/R/comments-utils.R index c9c5fab5..b94ac4e2 100644 --- a/R/comments-utils.R +++ b/R/comments-utils.R @@ -55,17 +55,7 @@ relative_path <- function(path) { if (is.null(path) || path == "default" || path == "user supplied") { return(path) } - tryCatch( - { - config_path <- find_pharos_config_file() - if (grepl("No pharos.toml", config_path)) { - return(path) - } - root <- fs::path_dir(config_path) - as.character(fs::path_rel(path, start = root)) - }, - error = function(e) path - ) + tryCatch(to_config_relative(path), error = function(e) path) } #' Set source paths for comment fields diff --git a/inst/extdata/models/onecmt/run004/run004.lst b/inst/extdata/models/onecmt/run004/run004.lst index 9a7db0f7..48dd26cf 100644 --- a/inst/extdata/models/onecmt/run004/run004.lst +++ b/inst/extdata/models/onecmt/run004/run004.lst @@ -712,7 +712,4 @@ Days until program expires : 36 2.79E-01 4.48E-01 5.33E-01 7.74E-01 1.07E+00 1.23E+00 1.26E+00 1.69E+00 1.72E+00 - Elapsed finaloutput time in seconds: 0.01 - #CPUT: Total CPU Time in Seconds, 0.805 -Stop Time: -Thu Jan 8 15:09:13 UTC 2026 + diff --git a/src/rust/nonmem/src/model/mod.rs b/src/rust/nonmem/src/model/mod.rs index 2ef8814b..0c2fadf8 100644 --- a/src/rust/nonmem/src/model/mod.rs +++ b/src/rust/nonmem/src/model/mod.rs @@ -8,7 +8,7 @@ use std::path::Path; use crate::model::run_status::determine_run_status; use crate::utils::{find_output_file, get_comment_type, to_config_relative, validate_model_path}; -use hyperion_core::{ResultExt, extendr_err}; +use hyperion_core::{OptionExt, ResultExt, extendr_err}; pub mod check; pub mod comment_info; @@ -65,7 +65,19 @@ fn add_run_status_attr(model_robj: &mut Robj, path: &Path) -> Result<()> { if let Some(ext) = path.extension().and_then(|e| e.to_str()) && (ext == "mod" || ext == "ctl" || ext == "lst") { - let run_status = determine_run_status(path)?; + let stem = path + .file_stem() + .ok_or_extendr_err("Could not determine model file stem")? + .to_string_lossy() + .to_string(); + let parent = path + .parent() + .ok_or_extendr_err("Could not determine parent directory")?; + let run_dir = match ext { + "lst" => parent.to_path_buf(), + _ => parent.join(&stem), + }; + let run_status = determine_run_status(&run_dir, &stem)?; model_robj .set_attrib("run_status", run_status.to_string().into_robj()) .map_to_extendr_err("Failed to set run_status attribute")?; diff --git a/src/rust/nonmem/src/model/run_status.rs b/src/rust/nonmem/src/model/run_status.rs index 9e03f2cb..76500948 100644 --- a/src/rust/nonmem/src/model/run_status.rs +++ b/src/rust/nonmem/src/model/run_status.rs @@ -1,15 +1,13 @@ use std::fmt; -use std::path::{Path, PathBuf}; +use std::path::Path; use extendr_api::Result; use extendr_api::prelude::*; use fs_err as fs; use hyperion_core::{OptionExt, extendr_err}; -use nonmem::Model; -use nonmem::output_files::ext::ExtReader; -use crate::utils::{find_output_file, path_from_robj, resolve_ext_path}; +use crate::utils::{find_output_file, path_from_robj}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RunStatus { @@ -29,76 +27,34 @@ impl fmt::Display for RunStatus { } } -pub fn determine_run_status(path: impl AsRef) -> Result { - let path = path.as_ref(); - let stem = path - .file_stem() - .ok_or_extendr_err("Could not determine model file stem")? - .to_string_lossy() - .to_string(); - let parent = path - .parent() - .ok_or_extendr_err("Could not determine model file parent directory")?; - let run_dir = match path.extension().and_then(|e| e.to_str()) { - Some("lst") => parent.to_path_buf(), - _ => parent.join(&stem), - }; - +/// Determine the run status from on-disk outputs. +/// +/// `Run` requires the `.lst` to contain NONMEM's "Stop Time:" marker, which is +/// written at run termination regardless of whether estimation or covariance +/// succeeded. `Running` means the `.lst` exists but the marker is absent. +/// `NotRun` means neither the run directory nor the `.lst` exists. +pub fn determine_run_status(run_dir: &Path, stem: &str) -> Result { if !run_dir.exists() { return Ok(RunStatus::NotRun); } - - let ext_path = resolved_ext_for_run(path, &run_dir, &stem); - let lst_path = run_dir.join(format!("{}.lst", stem)); - - let ext_exists = ext_path.exists(); - let lst_exists = lst_path.exists(); - - if !ext_exists && !lst_exists { + let lst_path = run_dir.join(format!("{stem}.lst")); + if !lst_path.exists() { return Ok(RunStatus::NotRun); } - - if ext_exists && lst_exists { - let ext_reader = ExtReader::default().final_estimates_only(); - if let Ok(tables) = ext_reader.parse_file(&ext_path) - && tables.iter().any(|t| !t.rows.is_empty()) - { - return Ok(RunStatus::Run); - } + if lst_indicates_completion(&lst_path) { + return Ok(RunStatus::Run); } - Ok(RunStatus::Running) } -/// Resolve the .ext path for a run, honoring `$EST FILE=` when the source -/// model can be parsed. Falls back to `{stem}.ext` in `run_dir` otherwise. -fn resolved_ext_for_run(input_path: &Path, run_dir: &Path, stem: &str) -> PathBuf { - let default = run_dir.join(format!("{stem}.ext")); - - // Locate the source .mod/.ctl. For .mod/.ctl input use it directly; for - // .lst input (which lives inside run_dir) the source is one level up. - let source = match input_path.extension().and_then(|e| e.to_str()) { - Some("mod") | Some("ctl") => input_path.to_path_buf(), - _ => match run_dir.parent() { - Some(p) => { - let mod_candidate = p.join(format!("{stem}.mod")); - if mod_candidate.exists() { - mod_candidate - } else { - p.join(format!("{stem}.ctl")) - } - } - None => return default, - }, - }; - - let Ok(content) = fs::read_to_string(&source) else { - return default; - }; - let Ok(model) = Model::parse(&content) else { - return default; +fn lst_indicates_completion(lst_path: &Path) -> bool { + let Ok(content) = fs::read_to_string(lst_path) else { + return false; }; - resolve_ext_path(&model, run_dir, stem) + content + .lines() + .rev() + .any(|line| line.trim_start().starts_with("Stop Time:")) } /// Determine run status for a model path, run directory, or model object. @@ -127,7 +83,20 @@ pub fn get_run_status(input: Robj) -> Result { } } - let status = determine_run_status(&path)?; + let stem = path + .file_stem() + .ok_or_extendr_err("Could not determine model file stem")? + .to_string_lossy() + .to_string(); + let parent = path + .parent() + .ok_or_extendr_err("Could not determine model file parent directory")?; + let run_dir = match path.extension().and_then(|e| e.to_str()) { + Some("lst") => parent.to_path_buf(), + _ => parent.join(&stem), + }; + + let status = determine_run_status(&run_dir, &stem)?; Ok(status.to_string().into_robj()) } @@ -150,54 +119,45 @@ mod tests { #[test] fn test_determine_run_status_run() { - let lst_file = test_data_dir().join("run001/run001.lst"); - let status = determine_run_status(&lst_file).unwrap(); + let run_dir = test_data_dir().join("run001"); + let status = determine_run_status(&run_dir, "run001").unwrap(); assert_eq!(status, RunStatus::Run); } #[test] fn test_determine_run_status_running() { - let lst_file = test_data_dir().join("run001-running/run001.lst"); - let status = determine_run_status(&lst_file).unwrap(); + let run_dir = test_data_dir().join("run001-running"); + let status = determine_run_status(&run_dir, "run001").unwrap(); assert_eq!(status, RunStatus::Running); } #[test] fn test_determine_run_status_not_run() { + // run_dir doesn't exist let temp_dir = TempDir::new().unwrap(); - let mod_file = temp_dir.path().join("run001.mod"); - fs::write(&mod_file, "test content").unwrap(); - - let status = determine_run_status(&mod_file).unwrap(); + let run_dir = temp_dir.path().join("run001"); + let status = determine_run_status(&run_dir, "run001").unwrap(); assert_eq!(status, RunStatus::NotRun); } #[test] fn test_determine_run_status_running_early() { - // NONMEM creates .lst before .ext during early execution + // .lst exists without "Stop Time:" => still running let temp_dir = TempDir::new().unwrap(); - let mod_file = temp_dir.path().join("run001.mod"); - fs::write(&mod_file, "test content").unwrap(); - let run_dir = temp_dir.path().join("run001"); fs::create_dir(&run_dir).unwrap(); - fs::write(run_dir.join("run001.lst"), "lst content").unwrap(); - - let status = determine_run_status(&mod_file).unwrap(); + fs::write(run_dir.join("run001.lst"), "partial lst content\n").unwrap(); + let status = determine_run_status(&run_dir, "run001").unwrap(); assert_eq!(status, RunStatus::Running); } #[test] fn test_determine_run_status_not_run_empty_run_dir() { - // Run directory exists but contains no output files + // run_dir exists but no .lst inside let temp_dir = TempDir::new().unwrap(); - let mod_file = temp_dir.path().join("run001.mod"); - fs::write(&mod_file, "test content").unwrap(); - let run_dir = temp_dir.path().join("run001"); fs::create_dir(&run_dir).unwrap(); - - let status = determine_run_status(&mod_file).unwrap(); + let status = determine_run_status(&run_dir, "run001").unwrap(); assert_eq!(status, RunStatus::NotRun); } } diff --git a/src/rust/nonmem/test_data/run001-running/pharos_end.json b/src/rust/nonmem/test_data/run001-running/pharos_end.json deleted file mode 100644 index c33c1a79..00000000 --- a/src/rust/nonmem/test_data/run001-running/pharos_end.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "start": "2025-09-30T15:10:00+00:00", - "end": "2025-09-30T15:10:20+00:00", - "runtime_ms": 20203, - "files_copied": [], - "output_files_rewrites": { - "run001.tab": "run001.tab", - "run001par.tab": "run001par.tab" - }, - "output_files_hashes": [ - { - "filename": "run001.ext", - "hashes": { - "blake3": "3d02a971fecf60ef371a73cf03d545d628a7387b775b7d2bd896a2a9316b91e2" - } - }, - { - "filename": "run001.grd", - "hashes": { - "blake3": "eaba2933d12232f11a69f42a764640a368b6749dd63ed7e8be40209d47018406" - } - }, - { - "filename": "run001.lst", - "hashes": { - "blake3": "03f24ac8b78209d9231c1e4459cb665c7fd6bc1ee667b2fb20e98a44e71c5a94" - } - }, - { - "filename": "run001.shk", - "hashes": { - "blake3": "9370bd17dbc898ef4f4c8ad77a0a60daddf5bf024e87f70f6622d893f807eed4" - } - }, - { - "filename": "run001.tab", - "hashes": { - "blake3": "c0cd30f4ae77dc53f5cf2695d578a5a341aa175a065083c075496bfd358a05d2" - } - }, - { - "filename": "run001par.tab", - "hashes": { - "blake3": "50df98a5b9be252d35ceeec6ad326267c1b18aeec68e735a9fcd0d483b2ff205" - } - } - ] -} \ No newline at end of file diff --git a/src/rust/nonmem/test_data/run001-running/run001.lst b/src/rust/nonmem/test_data/run001-running/run001.lst index 67ced16f..b267fdc5 100644 --- a/src/rust/nonmem/test_data/run001-running/run001.lst +++ b/src/rust/nonmem/test_data/run001-running/run001.lst @@ -415,6 +415,3 @@ Days until program expires : 139 + 0.00E+00 1.00E-01 Elapsed finaloutput time in seconds: 0.05 - #CPUT: Total CPU Time in Seconds, 0.443 -Stop Time: -Tue Sep 30 15:10:20 UTC 2025 From 3addc9b3c85bce5ead9e17b7ad20531dbd8c1575 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Fri, 15 May 2026 14:56:05 -0400 Subject: [PATCH 40/41] update read_ext_file --- DESCRIPTION | 4 +- NEWS.md | 2 + R/model-methods.R | 2 +- man/hyperion-package.Rd | 1 - src/rust/nonmem/src/model/parameters.rs | 61 +++----------- src/rust/nonmem/src/output_files/ext.rs | 59 ++++++-------- src/rust/nonmem/src/utils.rs | 101 +++++++++++++++++++++++- 7 files changed, 136 insertions(+), 94 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index f1d41531..bc57b02a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -32,7 +32,6 @@ Suggests: withr VignetteBuilder: knitr Imports: - fs, cli, knitr, lifecycle, @@ -41,4 +40,5 @@ Imports: tomledit Config/testthat/edition: 3 URL: https://a2-ai.github.io/hyperion-docs, https://github.com/a2-ai/hyperion -Config/roxygen2/version: 8.0.0 +Config/roxygen2/version: 7.3.3 +RoxygenNote: 7.3.3 diff --git a/NEWS.md b/NEWS.md index a89c8ad6..57b4059d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -37,6 +37,8 @@ structured grammar; `"type1"` remains strict structured. Replaces `use_type1_comments()`. - `summary()` includes `model_file` in its output. +- `read_ext_file()` accepts a `hyperion_nonmem_model` object in addition to + the previously-supported path forms. - `get_model_lineage(verbose = TRUE)` renders the lineage as a flat table with Model, Parent, Description, Tags, Model Hash, and Dataset Hash columns instead of the tree view. diff --git a/R/model-methods.R b/R/model-methods.R index 62af308a..c9274907 100644 --- a/R/model-methods.R +++ b/R/model-methods.R @@ -133,7 +133,7 @@ build_running_summary <- function(object, n_iterations) { iterations <- tryCatch( { ext_data <- read_ext_file( - model_path, + object, parameters_only = TRUE, only_last = TRUE ) diff --git a/man/hyperion-package.Rd b/man/hyperion-package.Rd index ea90f8a6..238b4850 100644 --- a/man/hyperion-package.Rd +++ b/man/hyperion-package.Rd @@ -141,7 +141,6 @@ Useful links: Authors: \itemize{ - \item Matt Smith \email{matthews@a2-ai.com} \item Vincent Prouillet \email{vincent@a2-ai.com} \item Mossa M. Reimert \email{mossa@a2-ai.com} \item Devin Pastoor \email{devin@a2-ai.com} diff --git a/src/rust/nonmem/src/model/parameters.rs b/src/rust/nonmem/src/model/parameters.rs index 159757bb..37bba5d2 100644 --- a/src/rust/nonmem/src/model/parameters.rs +++ b/src/rust/nonmem/src/model/parameters.rs @@ -2,23 +2,18 @@ use extendr_api::Result; use extendr_api::deserializer::from_robj; use extendr_api::prelude::*; -use fs_err as fs; use std::cmp::Ordering; -use std::ffi::OsStr; -use std::path::PathBuf; // pharos nonmem crate -use nonmem::{ - Model, - output_files::{ext::get_parameter_estimates, shk::ShkReader}, -}; +use nonmem::Model; +use nonmem::output_files::{ext::get_parameter_estimates, shk::ShkReader}; use crate::{ output_files::ext::create_ext_reader, output_files::{OMEGA, ParameterRow, ParameterRowBuilder, SIGMA, THETA, build_parameters_df}, - utils::{find_output_file, get_comment_type, path_from_robj, resolve_ext_path}, + utils::{find_output_file, get_comment_type, load_model_from_input, resolve_ext_path}, }; -use hyperion_core::{OptionExt, ResultExt, extendr_err}; +use hyperion_core::{ResultExt, extendr_err}; /// Extract numeric indices from a parameter name for sorting. /// @@ -116,53 +111,14 @@ pub fn get_parameters( ) -> Result { let ext_reader = create_ext_reader(None, None, only_method, only_last)?; - // Resolve the search path from either a string or model object - let search_path = path_from_robj(&path, false)?; - // If .ext file, use parent directory; otherwise use path as-is - let search_path = if search_path.extension() == Some(OsStr::new("ext")) { - search_path - .parent() - .map(|p| p.to_path_buf()) - .unwrap_or_else(|| PathBuf::from(".")) - } else { - search_path - }; + let loc = load_model_from_input(&path)?; - let shk_data = match find_output_file(&search_path, "shk") { + let shk_data = match find_output_file(&loc.run_dir, "shk") { Ok(p) => ShkReader.parse_file(p).unwrap_or_default(), Err(_) => Vec::new(), }; - let model_path = - find_output_file(&search_path, "mod").or_else(|_| find_output_file(&search_path, "ctl"))?; - let content = fs::read_to_string(&model_path).map_to_extendr_err("")?; - - let model = match Model::parse(&content) { - Ok(model) => model, - Err(diags) => { - let msg = diags - .iter() - .map(|d| d.render(model_path.as_path(), &content)) - .collect::>() - .join("\n"); - return Err(extendr_err!("Failed to read model file:\n{msg}")); - } - }; - - let stem = model_path - .file_stem() - .ok_or_extendr_err("Could not determine model file stem")? - .to_string_lossy() - .to_string(); - let run_dir = if search_path.is_dir() { - search_path.clone() - } else { - search_path - .parent() - .ok_or_extendr_err("Could not determine parent directory")? - .join(&stem) - }; - let ext_path = resolve_ext_path(&model, &run_dir, &stem); + let ext_path = resolve_ext_path(&loc.model, &loc.run_dir, &loc.stem); if !ext_path.exists() { return Err(extendr_err!( "Output file not found: {}", @@ -171,7 +127,8 @@ pub fn get_parameters( } let comment_type = get_comment_type(); - let parameter_names = model + let parameter_names = loc + .model .get_parameter_names(comment_type) .map_to_extendr_err("Failed to get model parameter names")?; diff --git a/src/rust/nonmem/src/output_files/ext.rs b/src/rust/nonmem/src/output_files/ext.rs index 633b2180..d0d45902 100644 --- a/src/rust/nonmem/src/output_files/ext.rs +++ b/src/rust/nonmem/src/output_files/ext.rs @@ -1,16 +1,14 @@ use extendr_api::Result; use extendr_api::{Robj, prelude::*}; -use fs_err as fs; use std::ffi::OsStr; use std::path::{Path, PathBuf}; //pharos nonmem crate -use nonmem::Model; use nonmem::estimation; use nonmem::output_files::ext::{EstimationTable, ExtReader}; -use crate::utils::{find_output_file, resolve_ext_path, to_syntactic_name}; +use crate::utils::{find_output_file, load_model_from_input, resolve_ext_path, to_syntactic_name}; use hyperion_core::{OptionExt, ResultExt, extendr_err}; /// Extract .ext files from path (single file or directory) @@ -239,50 +237,37 @@ fn fix_parameter_values(list: List, param_names: &[String]) -> Result { /// } #[extendr] pub fn read_ext_file( - path: &str, + path: Robj, #[extendr(default = "NULL")] line_prefixes: Option>, #[extendr(default = "FALSE")] parameters_only: Option, #[extendr(default = "NULL")] only_method: Option<&str>, #[extendr(default = "TRUE")] only_last: Option, ) -> Result { let ext_reader = create_ext_reader(line_prefixes, parameters_only, only_method, only_last)?; - let input = Path::new(path); - let ext_path = if input.extension() == Some(OsStr::new("ext")) { - find_output_file(input, "ext")? - } else { - let model_path = - find_output_file(input, "mod").or_else(|_| find_output_file(input, "ctl"))?; - let content = fs::read_to_string(&model_path).map_to_extendr_err("")?; - let model = Model::parse(&content) - .map_err(|_| extendr_err!("Failed to parse model: {}", model_path.display()))?; - let stem = model_path - .file_stem() - .ok_or_extendr_err("Could not determine model file stem")? - .to_string_lossy() - .to_string(); - let run_dir = if input.is_dir() { - input.to_path_buf() - } else { - input - .parent() - .ok_or_extendr_err("Could not determine parent directory")? - .join(&stem) - }; - let resolved = resolve_ext_path(&model, &run_dir, &stem); - if !resolved.exists() { - return Err(extendr_err!( - "Output file not found: {}", - resolved.display() - )); - } - resolved - }; - + let ext_path = resolve_ext_input(&path)?; let tables = ext_reader.parse_file(ext_path).map_to_extendr_err("")?; - estimation_tables_to_dataframe(tables) } +/// Accepts a `hyperion_nonmem_model` object, a `.mod`/`.ctl` path, a run +/// directory, or an existing `.ext` path. For `.ext` paths the file is used +/// directly; everything else routes through `load_model_from_input` so +/// `$EST FILE=` overrides are honored. +fn resolve_ext_input(input: &Robj) -> Result { + if let Some(s) = input.as_str() { + let path = Path::new(s); + if path.extension() == Some(OsStr::new("ext")) { + return find_output_file(path, "ext"); + } + } + let loc = load_model_from_input(input)?; + let resolved = resolve_ext_path(&loc.model, &loc.run_dir, &loc.stem); + if !resolved.exists() { + return Err(extendr_err!("Ext file not found: {}", resolved.display())); + } + Ok(resolved) +} + /// Gets all final estimates from an ext file or vector of ext files /// /// @param paths path to directory containing ext files (including subdirectories), single ext file, or vector of ext file paths diff --git a/src/rust/nonmem/src/utils.rs b/src/rust/nonmem/src/utils.rs index 303ec410..56b180d1 100644 --- a/src/rust/nonmem/src/utils.rs +++ b/src/rust/nonmem/src/utils.rs @@ -1,4 +1,5 @@ use extendr_api::Result; +use extendr_api::deserializer::from_robj; use extendr_api::prelude::*; use extendr_api::serializer::to_robj; @@ -8,6 +9,7 @@ use std::path::{Path, PathBuf}; // pharos config and nonmem crate use config::{CONFIG_FILENAME, CommentType, Config, NonmemConfig, to_root_relative}; use nonmem::Model; +use nonmem::output_files::resolve_estimation_files; // hyperion core use hyperion_core::{OptionExt, ResultExt, extendr_err, find_config_dir}; @@ -97,6 +99,103 @@ pub fn find_output_file(input_path: impl AsRef, extension: &str) -> Result } } +/// Parsed model bundled with its on-disk locations, returned by +/// [`load_model_from_input`]. +pub struct ModelLocation { + pub model: Model, + /// The `.mod`/`.ctl` file on disk that was parsed. + pub source: PathBuf, + /// The run output directory (containing `.lst`, `.ext`, etc.). + pub run_dir: PathBuf, + /// The model's stem (e.g. `"run001"`). + pub stem: String, +} + +/// Resolve a polymorphic input to a parsed Model plus its on-disk locations. +/// +/// Accepted inputs: +/// - `hyperion_nonmem_model` Robj (uses its `model_source` attribute). +/// - String path to a `.mod` or `.ctl` file. +/// - String path to a run directory, or any file inside a run directory +/// (e.g. `.ext`, `.lst`, `_metadata.json`). The model copy in the run +/// directory is used as the source. +pub fn load_model_from_input(input: &Robj) -> Result { + if input.inherits("hyperion_nonmem_model") { + let model: Model = from_robj(input)?; + let source = path_from_robj(input, false)?; + return assemble_top_level(model, source); + } + if let Some(s) = input.as_str() { + let path = Path::new(s); + // Direct .mod or .ctl file input. + if let Some(ext) = path.extension().and_then(|e| e.to_str()) + && (ext == "mod" || ext == "ctl") + && path.exists() + { + let model = parse_model_from_disk(path)?; + return assemble_top_level(model, path.to_path_buf()); + } + // Anything else (directory, _metadata.json, .lst, .ext, etc.): locate + // the in-run-dir model copy. `find_output_file` handles directories + // and files adjacent to the run dir (like `_metadata.json`); for files + // that live *inside* the run dir (`.lst`, `.ext`), it would compute a + // doubly-nested wrong path, so fall back to using the file's parent + // as the run dir. + let source = find_output_file(path, "mod") + .or_else(|_| find_output_file(path, "ctl")) + .or_else(|_| { + let parent = path + .parent() + .ok_or_extendr_err("Could not determine parent directory")?; + find_output_file(parent, "mod").or_else(|_| find_output_file(parent, "ctl")) + })?; + let stem = source + .file_stem() + .ok_or_extendr_err("Could not determine model file stem")? + .to_string_lossy() + .to_string(); + let run_dir = source + .parent() + .ok_or_extendr_err("Could not determine run directory")? + .to_path_buf(); + let model = parse_model_from_disk(&source)?; + return Ok(ModelLocation { + model, + source, + run_dir, + stem, + }); + } + Err(extendr_err!( + "Expected a hyperion_nonmem_model object or a path to a model file/directory" + )) +} + +/// Assemble a `ModelLocation` for a source path adjacent to its run dir +/// (the conventional `parent/stem.mod` + `parent/stem/` layout). +fn assemble_top_level(model: Model, source: PathBuf) -> Result { + let stem = source + .file_stem() + .ok_or_extendr_err("Could not determine model file stem")? + .to_string_lossy() + .to_string(); + let run_dir = source + .parent() + .ok_or_extendr_err("Could not determine model parent directory")? + .join(&stem); + Ok(ModelLocation { + model, + source, + run_dir, + stem, + }) +} + +fn parse_model_from_disk(path: &Path) -> Result { + let content = fs::read_to_string(path).map_to_extendr_err("Failed to read model file")?; + Model::parse(&content).map_err(|_| extendr_err!("Failed to parse model: {}", path.display())) +} + /// Resolve the final `.ext` file path for a model, honoring `$EST FILE=`. /// /// Returns the `.ext` path that the *last* `$EST` writes to, after applying @@ -105,7 +204,7 @@ pub fn find_output_file(input_path: impl AsRef, extension: &str) -> Result /// the model has no `$EST FILE=` overrides. pub fn resolve_ext_path(model: &Model, run_dir: &Path, stem: &str) -> PathBuf { let default = run_dir.join(format!("{stem}.ext")); - nonmem::output_files::resolve_estimation_files(model, run_dir, &default) + resolve_estimation_files(model, run_dir, &default) .pop() .unwrap_or(default) } From 89814790c82a826f3588a391e6e122d30ba097de Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Fri, 15 May 2026 15:34:55 -0400 Subject: [PATCH 41/41] check vers --- .github/workflows/R-CMD-check.yaml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index d5d16aa6..ca81c09b 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -39,11 +39,25 @@ jobs: run: | git config --global url."https://${{ secrets.PRIVATE_REPO_ACCESS_TOKEN }}@github.com/".insteadOf "https://github.com/" - - uses: dtolnay/rust-toolchain@master + - uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ matrix.config.rust-version }} targets: ${{ matrix.config.rust-target }} + - name: Ensure rustup cargo bin is first on PATH + if: runner.os != 'Windows' + run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH + + - name: Verify Rust toolchain + if: runner.os != 'Windows' + run: | + which rustup + which rustc + which cargo + rustup show + rustc --version + cargo --version + # Add rust-cache for faster builds - name: Cache Rust dependencies uses: Swatinem/rust-cache@v2 @@ -95,7 +109,7 @@ jobs: echo "All system dependencies are present" fi - name: rv-sync - run: rv sync + run: rv sync -vvv if: runner.os != 'Windows' - name: rv-sync (Windows)