From 2ebc959ae8d25edcc158c7e7a3d4e6231641308f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20D=2E=20Ot=C3=A1lvaro?= Date: Mon, 25 May 2026 11:23:25 +0100 Subject: [PATCH 1/7] dsl - initial version --- Cargo.lock | 502 ++- DESCRIPTION | 2 +- NAMESPACE | 5 +- R/PM_cov.R | 39 +- R/PM_cycle.R | 48 +- R/PM_final.R | 18 +- R/PM_model.R | 420 +- R/PM_op.R | 44 +- R/PM_parse.R | 23 +- R/PM_pop.R | 52 +- R/PM_post.R | 52 +- R/PM_result.R | 50 +- R/PM_sim.R | 3238 +++++++-------- R/PM_valid.R | 2 +- R/PMbuild.R | 40 +- R/extendr-wrappers.R | 52 +- R/model_dsl.R | 1275 ++++++ R/model_transpiler.R | 594 --- inst/Examples/Rscript/sylvain.R | 28 + inst/Examples/Rscript/sylvain_sim.R | 41 + inst/Examples/Runs/sylvain_sim/simulation.csv | 3463 +++++++++++++++++ inst/Examples/Runs/sylvain_sim/theta.csv | 3 + inst/Examples/src/Dapto_Lyon.csv | 980 +++++ man/PM_cov.Rd | 227 +- man/PM_cycle.Rd | 200 +- man/PM_data.Rd | 378 +- man/PM_final.Rd | 203 +- man/PM_fit.Rd | 180 +- man/PM_model.Rd | 491 ++- man/PM_op.Rd | 223 +- man/PM_opt.Rd | 174 +- man/PM_pop.Rd | 195 +- man/PM_post.Rd | 195 +- man/PM_pri.Rd | 97 +- man/PM_pta.Rd | 250 +- man/PM_result.Rd | 526 ++- man/PM_sim.Rd | 401 +- man/PM_valid.Rd | 226 +- man/compile_model.Rd | 26 - man/dummy_compile.Rd | 18 - man/fit.Rd | 10 +- man/mic1.Rd | 2 +- man/model_parameters.Rd | 8 +- man/plot.PM_cov.Rd | 28 +- man/plot.PM_cycle.Rd | 26 +- man/plot.PM_data.Rd | 24 +- man/plot.PM_final.Rd | 28 +- man/plot.PM_model.Rd | 24 +- man/plot.PM_op.Rd | 28 +- man/plot.PM_opt.Rd | 24 +- man/plot.PM_pop.Rd | 24 +- man/plot.PM_post.Rd | 24 +- man/plot.PM_pta.Rd | 36 +- man/plot.PM_sim.Rd | 28 +- man/plot.PM_valid.Rd | 24 +- man/plot.PMvalid.Rd | 2 +- man/round2.Rd | 2 +- man/simulate_all.Rd | 10 +- man/simulate_one.Rd | 10 +- man/temporary_path.Rd | 15 - man/validate_model_source.Rd | 22 + src/rust/Cargo.toml | 6 +- src/rust/src/executor.rs | 64 +- src/rust/src/lib.rs | 556 ++- src/rust/src/logs.rs | 129 +- src/rust/src/settings.rs | 412 +- tests/.dsl_runtime_validation.log | 286 ++ tests/testthat/Rplots.pdf | Bin 4712 -> 5440 bytes tests/testthat/helper-models.R | 24 - tests/testthat/test-analytical-indexing.R | 20 +- tests/testthat/test-book-examples.R | 3 +- tests/testthat/test-data-models.R | 8 +- tests/testthat/test-fit-cache-option.R | 60 + tests/testthat/test-fit-log-controls.R | 135 + tests/testthat/test-fit-progress-option.R | 79 + tests/testthat/test-fixed-error-models.R | 131 + tests/testthat/test-pm-fit-prior-parameters.R | 13 +- tests/testthat/test-pm-load-binary-fallback.R | 92 - tests/testthat/test-pm-load-dsl-roundtrip.R | 72 + tests/testthat/test-pm-model-guards.R | 4 +- .../testthat/test-pmcore-pharmsol-migration.R | 108 +- 81 files changed, 12173 insertions(+), 5409 deletions(-) create mode 100644 R/model_dsl.R delete mode 100755 R/model_transpiler.R create mode 100644 inst/Examples/Rscript/sylvain.R create mode 100644 inst/Examples/Rscript/sylvain_sim.R create mode 100644 inst/Examples/Runs/sylvain_sim/simulation.csv create mode 100644 inst/Examples/Runs/sylvain_sim/theta.csv create mode 100644 inst/Examples/src/Dapto_Lyon.csv delete mode 100644 man/compile_model.Rd delete mode 100644 man/dummy_compile.Rd delete mode 100644 man/temporary_path.Rd create mode 100644 man/validate_model_source.Rd create mode 100644 tests/.dsl_runtime_validation.log create mode 100644 tests/testthat/test-fit-cache-option.R create mode 100644 tests/testthat/test-fit-log-controls.R create mode 100644 tests/testthat/test-fit-progress-option.R create mode 100644 tests/testthat/test-fixed-error-models.R delete mode 100644 tests/testthat/test-pm-load-binary-fallback.R create mode 100644 tests/testthat/test-pm-load-dsl-roundtrip.R diff --git a/Cargo.lock b/Cargo.lock index a05891b15..6eaeca44c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anyhow" version = "1.0.102" @@ -39,6 +45,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + [[package]] name = "argmin" version = "0.11.0" @@ -86,6 +98,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.11.1" @@ -106,6 +124,9 @@ name = "bumpalo" version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +dependencies = [ + "allocator-api2", +] [[package]] name = "bytemuck" @@ -160,6 +181,15 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "cobyla" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae06ba097459ee2f913ff638c17f87acddcf4a0e30cdf14102ce023efc61ad37" +dependencies = [ + "libc", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -178,6 +208,184 @@ dependencies = [ "libc", ] +[[package]] +name = "cranelift" +version = "0.131.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4df9a80b0e50668699b52cc4a4ffa10e423df6ad4fd0150e3f983efc2bd7e876" +dependencies = [ + "cranelift-codegen", + "cranelift-frontend", + "cranelift-module", +] + +[[package]] +name = "cranelift-assembler-x64" +version = "0.131.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008f1a8d1da5074ad858f398775a6d1989031892e46927df5ed18d3be1ed8717" +dependencies = [ + "cranelift-assembler-x64-meta", +] + +[[package]] +name = "cranelift-assembler-x64-meta" +version = "0.131.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fd76237df1f4e26edb5ad7971d20280ed1e193331fd257f1b4e4dfefd88dda2" +dependencies = [ + "cranelift-srcgen", +] + +[[package]] +name = "cranelift-bforest" +version = "0.131.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380f0bc43e535df6855bbee649efb00bde39c3f33434c47c8e10ac836d21bf47" +dependencies = [ + "cranelift-entity", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-bitset" +version = "0.131.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4811e3e4502de04257e90c0a93225b56d9b85e0f9ad10b81446b415511009610" +dependencies = [ + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-codegen" +version = "0.131.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82ffadb34d497f3e76fb3b4baf764c24ba8a51512976a1b77f78bdbf8f4aa687" +dependencies = [ + "bumpalo", + "cranelift-assembler-x64", + "cranelift-bforest", + "cranelift-bitset", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-control", + "cranelift-entity", + "cranelift-isle", + "gimli", + "hashbrown 0.16.1", + "libm", + "log", + "regalloc2", + "rustc-hash", + "serde", + "smallvec", + "target-lexicon", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.131.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4f6992eb6faf086ddc7deaaa5f279abfe7f5fd5ae5709bd38253450fc7b945" +dependencies = [ + "cranelift-assembler-x64-meta", + "cranelift-codegen-shared", + "cranelift-srcgen", + "heck", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.131.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e1b2aad7d055925a4ea9cdbfa9d1d987f9dfc8ad6b708be28f901ac620a298" + +[[package]] +name = "cranelift-control" +version = "0.131.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a355348325e0a63b65c00def3871597b9fcc79d25456397010d16d872b3772" +dependencies = [ + "arbitrary", +] + +[[package]] +name = "cranelift-entity" +version = "0.131.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f4847d93ce2c80d2bff929aa1004dfb3ce2cf5d881f6ced54b8d654d967ba3" +dependencies = [ + "cranelift-bitset", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-frontend" +version = "0.131.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba24e5fe5242cc445e7892ef0a51a4351cf716e3a04ac7a3a05820d056c39818" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.131.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89bc2035de85c4f04ba7bd57eb5bd3a8b775235bf28852dbf87105115cb8919a" + +[[package]] +name = "cranelift-jit" +version = "0.131.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bf7a3c46c8b1ba6f4818f0cfe971d0cf875a28c7fded25b9fc0b75acbbb677a" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-control", + "cranelift-entity", + "cranelift-module", + "cranelift-native", + "libc", + "log", + "region", + "target-lexicon", + "wasmtime-internal-jit-icache-coherence", + "windows-sys 0.61.2", +] + +[[package]] +name = "cranelift-module" +version = "0.131.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680bd0df1ea88dc543eaa6aadf326200640be7603c5f36f9d5c1230b784ad8bc" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-control", +] + +[[package]] +name = "cranelift-native" +version = "0.131.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea6630c16921ab087792750f239d0c0173411e80179ca7c0ce0710ce9e7646a" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + +[[package]] +name = "cranelift-srcgen" +version = "0.131.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faa4bbad54fc28cc0da1f9a5d7f7f826ec8cafda3d503b401b2daaaa93c63ef0" + [[package]] name = "crossbeam" version = "0.8.4" @@ -271,6 +479,22 @@ dependencies = [ "memchr", ] +[[package]] +name = "ctor" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "424e0138278faeb2b401f174ad17e715c829512d74f3d1e81eb43365c2e0590e" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" + [[package]] name = "defer" version = "0.2.1" @@ -288,9 +512,9 @@ dependencies = [ [[package]] name = "diffsol" -version = "0.11.0" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b3d0679fef37a03d4a60ba9bf611bbc941ff748345f15caebf6f5194cc565d" +checksum = "34940fd335386fb9098732cacb5999c0062e722f3f7f5eaad2e642db3a3b64a7" dependencies = [ "faer", "faer-traits", @@ -314,6 +538,21 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dtor" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "404d02eeb088a82cfd873006cb713fe411306c7d182c344905e101fb1167d301" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + [[package]] name = "dyn-stack" version = "0.13.2" @@ -422,6 +661,16 @@ dependencies = [ "readonly", ] +[[package]] +name = "extendr-engine" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f871557c9444f6bf5938963dc69da6b220482cc80859c6d82b9df68f69b44c80" +dependencies = [ + "ctor", + "extendr-ffi", +] + [[package]] name = "extendr-ffi" version = "0.9.0" @@ -495,12 +744,24 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" +[[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "gemm" version = "0.19.0" @@ -688,6 +949,18 @@ dependencies = [ "wasip3", ] +[[package]] +name = "gimli" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7f043f89559805f8c7cacc432749b2fa0d0a0a9ee46ce47164ed5ba7f126c" +dependencies = [ + "fnv", + "hashbrown 0.16.1", + "indexmap", + "stable_deref_trait", +] + [[package]] name = "glam" version = "0.14.0" @@ -815,7 +1088,16 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash 0.2.0", ] [[package]] @@ -899,16 +1181,6 @@ version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" -[[package]] -name = "libloading" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" -dependencies = [ - "cfg-if", - "windows-link", -] - [[package]] name = "libm" version = "0.2.16" @@ -943,6 +1215,15 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + [[package]] name = "matchers" version = "0.2.0" @@ -1324,19 +1605,23 @@ dependencies = [ [[package]] name = "pharmsol" -version = "0.26.1" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de605b6c68d73a526d5247830088350df33bed2cdfdcd52fa00e86933a93a154" +checksum = "4bf81810d71f087d4b8a612a3e48ad404ac70b7d688ba6723e6437b3685bbfd3" dependencies = [ "ahash", "argmin", "argmin-math", + "cranelift", + "cranelift-jit", + "cranelift-module", + "cranelift-native", "csv", "diffsol", - "libloading", "moka", "nalgebra 0.34.2", "ndarray", + "pharmsol-dsl", "pharmsol-macros", "rand 0.10.1", "rand_distr 0.6.0", @@ -1348,12 +1633,23 @@ dependencies = [ "tracing", ] +[[package]] +name = "pharmsol-dsl" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02dc32b544cc99c93f3f59db69788a8edb31966ee57ca9c93a7f1af4f6777fe5" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "pharmsol-macros" -version = "0.1.0" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fbff5e3ed52098eb43dbf92bfb76de87661b73d9f486d28a0cb5f0a5d821f8" +checksum = "250cbc2df2aa89bc18b61215faad1ccc7424e3161e68010d63a7e4845d16c301" dependencies = [ + "pharmsol-dsl", "proc-macro2", "quote", "syn 2.0.117", @@ -1371,7 +1667,7 @@ version = "0.1.0" dependencies = [ "anyhow", "extendr-api", - "libloading", + "extendr-engine", "pmcore", "rayon", "tracing", @@ -1381,20 +1677,21 @@ dependencies = [ [[package]] name = "pmcore" version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56359b47b12acc12b49b61e98c2405f8750e586a3241e8417bdfa5c59d6aa71d" dependencies = [ "anyhow", "argmin", + "cobyla", "csv", "faer", "ndarray", "pharmsol", "rand 0.10.1", + "rand_distr 0.6.0", "rayon", "serde", "serde_json", "sobol_burley", + "statrs", "tracing", "tracing-subscriber", ] @@ -1654,7 +1951,7 @@ version = "11.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" dependencies = [ - "bitflags", + "bitflags 2.11.1", ] [[package]] @@ -1706,7 +2003,21 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.11.1", +] + +[[package]] +name = "regalloc2" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de2c52737737f8609e94f975dee22854a2d5c125772d4b1cf292120f4d45c186" +dependencies = [ + "allocator-api2", + "bumpalo", + "hashbrown 0.17.0", + "log", + "rustc-hash", + "smallvec", ] [[package]] @@ -1726,6 +2037,24 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "region" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b6ebd13bc009aef9cd476c1310d49ac354d36e240cf1bd753290f3dc7199a7" +dependencies = [ + "bitflags 1.3.2", + "libc", + "mach2", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + [[package]] name = "rustversion" version = "1.0.22" @@ -1887,6 +2216,12 @@ dependencies = [ "rayon", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + [[package]] name = "statrs" version = "0.18.0" @@ -1927,7 +2262,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01198a2debb237c62b6826ec7081082d951f46dbb64b0e8c7649a452230d1dfc" dependencies = [ - "bitflags", + "bitflags 2.11.1", "byteorder", "enum-as-inner", "libc", @@ -1941,6 +2276,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + [[package]] name = "thiserror" version = "1.0.69" @@ -2237,12 +2578,34 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags", + "bitflags 2.11.1", "hashbrown 0.15.5", "indexmap", "semver", ] +[[package]] +name = "wasmtime-internal-core" +version = "44.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4364d345719bba7fc4c435992ea1cb0c118f1e90a88c6e6f22a7a4fc507700c6" +dependencies = [ + "hashbrown 0.16.1", + "libm", +] + +[[package]] +name = "wasmtime-internal-jit-icache-coherence" +version = "44.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ba98c1492f530833e0d3cc17dbb0c3c57c9f1bb3b078ae44bb55a233e43eba" +dependencies = [ + "cfg-if", + "libc", + "wasmtime-internal-core", + "windows-sys 0.61.2", +] + [[package]] name = "web-time" version = "1.1.0" @@ -2293,13 +2656,22 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", ] [[package]] @@ -2311,48 +2683,112 @@ 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 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "wit-bindgen" version = "0.51.0" @@ -2417,7 +2853,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags", + "bitflags 2.11.1", "indexmap", "log", "serde", diff --git a/DESCRIPTION b/DESCRIPTION index 6cd3a70d2..95a323e14 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -87,4 +87,4 @@ LazyData: true LazyDataCompression: xz Roxygen: list(markdown = TRUE) SystemRequirements: Cargo (>= 1.82) (Rust's package manager), rustc - +RoxygenNote: 7.3.3 diff --git a/NAMESPACE b/NAMESPACE index dfd0b09b8..12404910d 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -20,6 +20,7 @@ S3method(plot,PMvalid) S3method(print,PM_compare) S3method(print,PMerr) S3method(print,PMnpc) +S3method(print,pm_dsl) S3method(print,summary.PM_cycle) S3method(print,summary.PM_data) S3method(print,summary.PM_final) @@ -102,11 +103,9 @@ export(clear_build) export(cli_ask) export(cli_df) export(click_plot) -export(compile_model) export(cor2cov) export(create_pmetrics_project) export(downloadR) -export(dummy_compile) export(export_plotly) export(fit) export(getCov) @@ -151,7 +150,6 @@ export(simulate_one) export(ss.PK) export(sub_plot) export(template) -export(temporary_path) export(three_comp_bolus) export(three_comp_bolus_cl) export(three_comp_iv) @@ -160,6 +158,7 @@ export(two_comp_bolus) export(two_comp_bolus_cl) export(two_comp_iv) export(two_comp_iv_cl) +export(validate_model_source) export(zBMI) importFrom(R6,R6Class) importFrom(bslib,accordion) diff --git a/R/PM_cov.R b/R/PM_cov.R index 93eb69049..1b10453db 100755 --- a/R/PM_cov.R +++ b/R/PM_cov.R @@ -107,15 +107,15 @@ PM_cov <- R6::R6Class( return(NULL) } - if (file.exists(file.path(path, "covs.csv"))) { - covs <- readr::read_csv(file = file.path(path, "covs.csv"), show_col_types = FALSE) - } else if (inherits(data, "PM_cov")) { # file not there, and already PM_cov + if (file.exists(file.path(path, "covariates.csv"))) { + covs <- readr::read_csv(file = file.path(path, "covariates.csv"), show_col_types = FALSE) + } else if (inherits(data, "PM_cov") && !is.null(data$data)) { # file not there, and already PM_cov class(data$data) <- c("PM_cov_data", "data.frame") return(data$data) } else { cli::cli_warn(c( "!" = "Unable to generate covariate-posterior information.", - "i" = "{.file {file.path(path, 'covs.csv')}} does not exist, and result does not have valid {.code PM_cov} object." + "i" = "{.file {file.path(path, 'covariates.csv')}} does not exist, and result does not have valid {.code PM_cov} object." )) return(NULL) } @@ -254,22 +254,21 @@ PM_cov <- R6::R6Class( #' @family PMplots plot.PM_cov <- function( - x, - formula, - line = list(lm = NULL, loess = NULL, ref = NULL), - marker = TRUE, - colors, - icen = "median", - include = NULL, exclude = NULL, - legend, - log = FALSE, - grid = TRUE, - xlab, ylab, - title, - stats = TRUE, - xlim, ylim, - print = TRUE, ... -) { + x, + formula, + line = list(lm = NULL, loess = NULL, ref = NULL), + marker = TRUE, + colors, + icen = "median", + include = NULL, exclude = NULL, + legend, + log = FALSE, + grid = TRUE, + xlab, ylab, + title, + stats = TRUE, + xlim, ylim, + print = TRUE, ...) { if (inherits(x, "PM_cov")) { x <- x$data } diff --git a/R/PM_cycle.R b/R/PM_cycle.R index ecff44bee..3e7be4e95 100755 --- a/R/PM_cycle.R +++ b/R/PM_cycle.R @@ -119,11 +119,11 @@ PM_cycle <- R6::R6Class( ), # end active private = list( make = function(data, path) { - if (file.exists(file.path(path, "cycles.csv"))) { - raw <- readr::read_csv(file = file.path(path, "cycles.csv"), show_col_types = FALSE) + if (file.exists(file.path(path, "iterations.csv"))) { + raw <- readr::read_csv(file = file.path(path, "iterations.csv"), show_col_types = FALSE) if (nrow(raw) == 0) { # posterior raw <- data.frame(cycle = 0, status = "Posterior") - write.csv(raw, file.path(path, "cycles.csv"), row.names = FALSE) + write.csv(raw, file.path(path, "iterations.csv"), row.names = FALSE) } } else if (inherits(data, "PM_cycle") & !is.null(data$data)) { # file not there, and already PM_cycle class(data$data) <- c("PM_cycle_data", "list") @@ -131,15 +131,15 @@ PM_cycle <- R6::R6Class( } else { cli::cli_warn(c( "!" = "Unable to generate cycle information.", - "i" = "{.file {file.path(path, 'cycles.csv')}} does not exist, and result does not have valid {.code PM_cycle} object." + "i" = "{.file {file.path(path, 'iterations.csv')}} does not exist, and result does not have valid {.code PM_cycle} object." )) return(NULL) } - if (file.exists(file.path(path, "pred.csv"))) { + if (file.exists(file.path(path, "predictions.csv"))) { op_raw <- readr::read_csv( - file = file.path(path, "pred.csv"), + file = file.path(path, "predictions.csv"), col_types = list( time = readr::col_double(), outeq = readr::col_integer(), @@ -158,7 +158,7 @@ PM_cycle <- R6::R6Class( } else { cli::cli_warn(c( "!" = "Unable to generate cycle information.", - "i" = "{.file {file.path(path, 'pred.csv')}} does not exist, and result does not have valid {.code PM_cycle} object." + "i" = "{.file {file.path(path, 'predictions.csv')}} does not exist, and result does not have valid {.code PM_cycle} object." )) return(NULL) } @@ -177,10 +177,17 @@ PM_cycle <- R6::R6Class( return(NULL) } + parameter_names <- if (!is.null(config$parameters) && length(config$parameters) > 0) { + config$parameters[[1]][["name"]] + } else { + unique(sub("\\.(mean|median|sd)$", "", names(raw)[grepl("\\.(mean|median|sd)$", names(raw))])) + } + num_params <- length(parameter_names) + # Run was a posterior if (raw$status[1] == "Posterior") { res <- list( - names = config$parameters$parameters$name, + names = parameter_names, objective = tibble(cycle = 0, neg2ll = NA, aic = NA, bic = NA), gamlam = NA, mean = NA, @@ -197,8 +204,6 @@ PM_cycle <- R6::R6Class( pivot_longer(cols = ends_with(c("mean", "median", "sd"))) |> separate_wider_delim(name, delim = ".", names = c("parameter", "statistic")) - num_params <- nrow((config$parameters$parameters)) - aic <- 2 * num_params + raw$neg2ll names(aic) <- raw$cycle bic <- num_params * log(length(unique(op_raw$id))) + raw$neg2ll @@ -269,7 +274,7 @@ PM_cycle <- R6::R6Class( status <- tail(cycle_data$status, 1) res <- list( - names = config$parameters$parameters$name, + names = parameter_names, objective = raw |> select(cycle, neg2ll) |> mutate(aic = aic, bic = bic), gamlam = gamlam, mean = mean, @@ -366,17 +371,16 @@ PM_cycle <- R6::R6Class( #' @family PMplots plot.PM_cycle <- function( - x, - line = TRUE, - marker = TRUE, - colors, - linetypes, - omit, - grid = TRUE, - xlab, ylab, - print = TRUE, - ... -) { + x, + line = TRUE, + marker = TRUE, + colors, + linetypes, + omit, + grid = TRUE, + xlab, ylab, + print = TRUE, + ...) { if (inherits(x, "PM_cycle")) { data <- x$data } else { diff --git a/R/PM_final.R b/R/PM_final.R index e72d31c6f..37bc9d6c0 100755 --- a/R/PM_final.R +++ b/R/PM_final.R @@ -258,7 +258,7 @@ PM_final <- R6::R6Class( if (file.exists(file.path(path, "settings.json"))) { config <- jsonlite::fromJSON(file.path(path, "settings.json")) - } else if (inherits(data, "PM_final")) { # file not there, and already PM_final + } else if (inherits(data, "PM_final") & !is.null(data$data)) { # file not there, and already PM_final class(data$data) <- c("PM_final_data", "data.frame") return(data$data) } else { @@ -345,12 +345,20 @@ PM_final <- R6::R6Class( sh <- varEBD / popSD**2 # ranges - ab <- config$parameters[[1]] |> - tibble::as_tibble() |> - dplyr::rename(par = name, min = lower, max = upper) + ab <- if (!is.null(config$parameters) && length(config$parameters) > 0) { + config$parameters[[1]] |> + tibble::as_tibble() |> + dplyr::rename(par = name, min = lower, max = upper) + } else { + tibble::tibble(par = par_names, min = NA_real_, max = NA_real_) + } - gridpts <- config$prior$Sobol[1] + gridpts <- if (!is.null(config$prior) && !is.null(config$prior$Sobol)) { + config$prior$Sobol[1] + } else { + nrow(theta) + } final <- list( popPoints = theta, diff --git a/R/PM_model.R b/R/PM_model.R index 71e442d4b..a4b7b718c 100644 --- a/R/PM_model.R +++ b/R/PM_model.R @@ -78,13 +78,11 @@ PM_model <- R6::R6Class( "PM_model", public = list( - #' @field model_list A list containing the model components built by translating - #' the original arguments into Rust + #' @field model_list A list containing normalized model metadata derived from + #' the original R model blocks model_list = NULL, #' @field arg_list A list containing the original arguments passed to the model arg_list = NULL, - #' @field binary_path The full path and filename of the compiled model - binary_path = NULL, #' @description #' This is the method to create a new `PM_model` object. #' @@ -436,7 +434,6 @@ PM_model <- R6::R6Class( } }) self$arg_list$x <- NULL - self$binary_path <- x$binary_path } else { cli::cli_abort(c( "x" = "Non supported input for {.arg x}: {typeof(x)}", @@ -498,12 +495,12 @@ PM_model <- R6::R6Class( } } - solver_rust <- NULL + normalized_solver <- NULL if (!is.null(self$arg_list$solver)) { if (type != "ODE") { msg <- c(msg, "{.arg solver} is only supported for ODE models.") } else { - solver_rust <- ode_solver_to_rust(self$arg_list$solver) + normalized_solver <- normalize_ode_solver(self$arg_list$solver) } } @@ -512,7 +509,7 @@ PM_model <- R6::R6Class( } else { NA_integer_ } - template_n_drug <- if (type == "Analytical") { + n_drug <- if (type == "Analytical") { max( get_max_index(model_template$arg_list$eqn, c("b", "bolus", "r", "rateiv")), if (!is.null(model_template$arg_list$lag)) get_max_index(model_template$arg_list$lag, "lag") else 0L, @@ -524,33 +521,13 @@ PM_model <- R6::R6Class( NA_integer_ } - # Number of equations and backend slot sizes + # Number of equations and channels n_eqn <- if (type == "Analytical") { template_n_eqn } else { get_assignments(self$arg_list$eqn, "dx") } - n_eqn_slots <- if (type == "Analytical") { - index_vector_size(template_n_eqn) - } else { - NA_integer_ - } n_out <- get_assignments(self$arg_list$out, "y") - n_out_slots <- if (type == "Analytical") { - index_vector_size(get_max_index(self$arg_list$out, "y")) - } else { - NA_integer_ - } - n_drug <- if (type == "Analytical") { - template_n_drug - } else { - NA_integer_ - } - n_drug_slots <- if (type == "Analytical") { - index_vector_size(template_n_drug) - } else { - NA_integer_ - } ## Get the names of the parameters parameters <- tolower(names(self$arg_list$pri)) @@ -671,66 +648,6 @@ PM_model <- R6::R6Class( } } # end parameter checks for Analytical model - # if Analytical, need to combine sec and eqn - if (type == "Analytical") { - # shell function - sec_eqn <- function() {} - sec_body <- if (!is.null(self$arg_list$sec)) as.list(body(self$arg_list$sec))[-1] else list() - # define the body of the shell function - body(sec_eqn) <- suppressWarnings(as.call(c( - quote(`{`), - as.list(body(self$arg_list$eqn))[-1], # remove outer `{` of f1 - sec_body # remove outer `{` of f2 - ))) - - # this will include template and equations in both sec and eqn - } - - # sec - # still needed for analytic, because these equations will be used - # in other blocks - - if (!is.null(self$arg_list$sec)) { - sec <- transpile_sec(self$arg_list$sec) - } else { - sec <- "" - } - - # eqn - if (type == "ODE") { - eqn <- transpile_ode_eqn(self$arg_list$eqn, parameters, covariates, sec) - } else if (type == "Analytical") { - eqn <- transpile_analytic_eqn(sec_eqn, parameters, covariates) - } - - # fa - if (!is.null(self$arg_list$fa)) { - fa <- transpile_fa(self$arg_list$fa, parameters, covariates, sec) - } else { - fa <- empty_fa() - } - - # lag - if (!is.null(self$arg_list$lag)) { - lag <- transpile_lag(self$arg_list$lag, parameters, covariates, sec) - } else { - lag <- empty_lag() - } - - # ini - if (!is.null(self$arg_list$ini)) { - ini <- transpile_ini(self$arg_list$ini, parameters, covariates, sec) - } else { - ini <- empty_ini() - } - - # out - if (!is.null(self$arg_list$out)) { - out <- transpile_out(self$arg_list$out, parameters, covariates, sec) - } else { - out <- empty_out() - } - # err if (is.null(self$arg_list$err)) { msg <- c(msg, "Error model is missing and required.") @@ -747,39 +664,18 @@ PM_model <- R6::R6Class( "user" } - # build the model list of rust components - model_list <- list( + self$model_list <- list( pri = self$arg_list$pri, - eqn = eqn, - sec = sec, - lag = lag, - fa = fa, - ini = ini, - out = out, n_eqn = n_eqn, - n_eqn_slots = n_eqn_slots, n_drug = n_drug, - n_drug_slots = n_drug_slots, n_out = n_out, - n_out_slots = n_out_slots, parameters = parameters, covariates = covariates, err = err, - solver = self$arg_list$solver, - name = name + solver = normalized_solver, + name = name, + type = type ) - # make everything lower case if a character vector - self$model_list <- purrr::map(model_list, \(x) { - if (is.character(x)) { - tolower(x) - } else { - x - } - }) - - # this one needs to be capital - self$model_list$type <- type - self$model_list$solver_rust <- solver_rust # Abort if errors @@ -1000,6 +896,21 @@ PM_model <- R6::R6Class( #' missing the globally maximally likely parameter value distribution, #' but the slower the run. #' + #' @param cache Logical. Whether to enable runtime prediction caching during fitting. + #' Default is `TRUE`. + #' + #' @param progress Logical. Whether to print runtime likelihood-matrix progress during + #' fitting. Default is `TRUE`. + #' + #' @param write_logs Logical. Whether to write fit tracing logs to `outputs/log.txt`. + #' Default is `FALSE`. + #' + #' @param stdout_logs Logical. Whether to print fit tracing logs in the R console. + #' Default is `TRUE`. + #' + #' @param log_level Character scalar. Fit tracing level. One of `"ERROR"`, `"WARN"`, + #' `"INFO"`, `"DEBUG"`, or `"TRACE"`. Default is `"INFO"`. + #' #' @param idelta How often to generate posterior predictions in units of time. #' Default is 0.1, which means a prediction is generated every 0.1 hours (6 minutes) #' if the unit of time is hours. Predictions are made at this interval until the time @@ -1039,6 +950,11 @@ PM_model <- R6::R6Class( cycles = 100, prior = "sobol", points = 100, + cache = TRUE, + progress = TRUE, + write_logs = FALSE, + stdout_logs = TRUE, + log_level = "INFO", idelta = 0.1, tad = 0, seed = 23, @@ -1088,15 +1004,17 @@ PM_model <- R6::R6Class( if (self$model_list$type == "ODE") { # only need to check these for ODE models bolus <- unique(data$standard_data$input[data$standard_data$dur == 0]) |> purrr::discard(~ is.na(.x)) infusion <- unique(data$standard_data$input[data$standard_data$dur > 0]) |> purrr::discard(~ is.na(.x)) + bolus_in_model <- collect_index_refs(self$arg_list$eqn, c("b", "bolus")) + infusion_in_model <- collect_index_refs(self$arg_list$eqn, c("r", "rateiv")) if (length(bolus) > 0) { - missing_bolus <- bolus[!stringr::str_detect(self$model_list$eqn, paste0("b\\[", bolus, "\\]"))] + missing_bolus <- bolus[!bolus %in% bolus_in_model] if (length(missing_bolus) > 0) { msg <- c(msg, "Bolus input(s) {paste(missing_bolus, collapse = ', ')} {?is/are} missing from the model equations. Use {.code b[{missing_bolus}]} or {.code bolus[{missing_bolus}]}, for example, to represent bolus inputs in the equations.") run_error <- run_error + 1 } } if (length(infusion) > 0) { - missing_infusion <- infusion[!stringr::str_detect(self$model_list$eqn, paste0("rateiv\\[", infusion, "\\]"))] + missing_infusion <- infusion[!infusion %in% infusion_in_model] if (length(missing_infusion) > 0) { msg <- c(msg, "Infusion input(s) {paste(missing_infusion, collapse = ', ')} {?is/are} missing from the model equations. Use {.code r[{missing_infusion}]} or {.code rateiv[{missing_infusion}]} , for example, to represent infusion inputs in the equations.") run_error <- run_error + 1 @@ -1105,7 +1023,7 @@ PM_model <- R6::R6Class( } # covariates - modelCov <- self$model_list$cov + modelCov <- self$model_list$covariates if (length(modelCov) > 0) { dataCov <- tolower(getCov(data)$covnames) missingCov <- modelCov[!modelCov %in% dataCov] @@ -1122,6 +1040,38 @@ PM_model <- R6::R6Class( run_error <- run_error + 1 } + if (!is.logical(cache) || length(cache) != 1 || is.na(cache)) { + msg <- c(msg, "{.arg cache} must be TRUE or FALSE.") + run_error <- run_error + 1 + } + + if (!is.logical(progress) || length(progress) != 1 || is.na(progress)) { + msg <- c(msg, "{.arg progress} must be TRUE or FALSE.") + run_error <- run_error + 1 + } + + if (!is.logical(write_logs) || length(write_logs) != 1 || is.na(write_logs)) { + msg <- c(msg, "{.arg write_logs} must be TRUE or FALSE.") + run_error <- run_error + 1 + } + + if (!is.logical(stdout_logs) || length(stdout_logs) != 1 || is.na(stdout_logs)) { + msg <- c(msg, "{.arg stdout_logs} must be TRUE or FALSE.") + run_error <- run_error + 1 + } + + valid_log_levels <- c("ERROR", "WARN", "INFO", "DEBUG", "TRACE") + if (!is.character(log_level) || length(log_level) != 1 || is.na(log_level)) { + msg <- c(msg, "{.arg log_level} must be one of {.val {valid_log_levels}}.") + run_error <- run_error + 1 + } else { + log_level <- toupper(log_level) + if (!(log_level %in% valid_log_levels)) { + msg <- c(msg, "{.arg log_level} must be one of {.val {valid_log_levels}}.") + run_error <- run_error + 1 + } + } + #### Algorithm #### algorithm <- toupper(algorithm) if (cycles == 0) { @@ -1374,11 +1324,17 @@ PM_model <- R6::R6Class( #### Save input objects #### dir.create(normalizePath(file.path(path_run, "inputs"), mustWork = FALSE), recursive = TRUE) - PM_data$new(data_filtered, quiet = TRUE)$save(normalizePath(file.path(path_run, "inputs", "gendata.csv"), mustWork = FALSE), header = FALSE) + runtime_data_path <- normalizePath(file.path(path_run, "inputs", "gendata.csv"), mustWork = FALSE) + PM_data$new(data_filtered, quiet = TRUE)$save(runtime_data_path, header = FALSE) + rewrite_runtime_route_labels(runtime_data_path, self) + model_source <- self$dsl() suppressWarnings( - saveRDS(list(data = data, model = self), file = normalizePath(file.path(path_run, "inputs", "fit.rds"), mustWork = FALSE)) + saveRDS( + list(data = data, model = self, dsl_source = model_source), + file = normalizePath(file.path(path_run, "inputs", "fit.rds"), mustWork = FALSE) + ) ) - file.copy(self$binary_path, normalizePath(file.path(path_run, "inputs"), mustWork = FALSE)) + writeLines(model_source, normalizePath(file.path(path_run, "inputs", "model.dsl"), mustWork = FALSE)) # Get ranges and calculate points ranges <- lapply(self$model_list$pri, function(x) { @@ -1402,13 +1358,17 @@ PM_model <- R6::R6Class( msg <- c(msg, "Run results were saved in folder '{.path {out_path}}'") fit_call <- function() { fit( - # defined in extendr-wrappers.R - model_path = normalizePath(self$binary_path), + model_source = model_source, data = normalizePath(file.path(path_run, "inputs", "gendata.csv")), params = list( ranges = ranges, # not important but needed for POSTPROB algorithm = algorithm, error_models = lapply(self$model_list$err, function(x) x$flatten()), + cache = cache, + progress = progress, + write_logs = write_logs, + stdout_logs = stdout_logs, + log_level = log_level, idelta = idelta, tad = tad, max_cycles = cycles, # will be hardcoded in Rust to 0 for POSTPROB @@ -1417,7 +1377,8 @@ PM_model <- R6::R6Class( seed = seed ), output_path = out_path, - kind = tolower(self$model_list$type) + kind = tolower(self$model_list$type), + solver = self$model_list$solver ) } rlang::try_fetch( @@ -1555,41 +1516,118 @@ PM_model <- R6::R6Class( temp_csv <- tempfile(fileext = ".csv") data$save(temp_csv, header = FALSE) + rewrite_runtime_route_labels(temp_csv, self) - if (is.null(self$binary_path)) { - self$compile(quiet = quiet) - if (is.null(self$binary_path)) { - cli::cli_abort(c("x" = "Model must be compiled before simulating.")) + self$compile(quiet = quiet) + sim <- simulate_all( + temp_csv, + self$dsl(), + theta, + kind = tolower(self$model_list$type), + solver = self$model_list$solver + ) + + return(sim) + }, + #' @description + #' Return the lowered model DSL. + #' @details + #' This method returns the pharmsol DSL source generated from the current model. + dsl = function() { + new_pm_dsl(private$build_model_dsl()) + }, + #' @description + #' Inspect the lowered model DSL and runtime validation result. + #' @details + #' This method is useful when you want to debug the exact DSL source that + #' Pmetrics sends to the runtime. It returns the lowered DSL, a line-numbered + #' copy, and either the rendered validation diagnostic or a success status. + #' @param quiet Logical, if TRUE, suppresses messages and DSL printing. + #' @return A list with `ok`, `stage`, `dsl`, `numbered_dsl`, and `diagnostic`. + debug_dsl = function(quiet = FALSE) { + model_source <- NULL + numbered_dsl <- NULL + diagnostic <- NULL + + model_source <- tryCatch( + as.character(self$dsl()), + error = function(e) { + diagnostic <<- conditionMessage(e) + return(NULL) + } + ) + + if (is.null(model_source)) { + if (!quiet) { + cli::cli_warn("Model DSL generation failed.") + cat(diagnostic, "\n", sep = "") } + + return(invisible(list( + ok = FALSE, + stage = "dsl", + dsl = NULL, + numbered_dsl = NULL, + diagnostic = diagnostic + ))) } - sim <- simulate_all(temp_csv, self$binary_path, theta, kind = tolower(self$model_list$type)) - return(sim) + dsl_lines <- strsplit(model_source, "\n", fixed = TRUE)[[1]] + numbered_lines <- sprintf("%4d | %s", seq_along(dsl_lines), dsl_lines) + numbered_dsl <- paste(numbered_lines, collapse = "\n") + + if (!quiet) { + cli::cli_inform(c("i" = "Model DSL:")) + writeLines(numbered_lines) + } + + ok <- tryCatch( + { + validate_model_source( + model_source = model_source, + kind = tolower(self$model_list$type), + solver = self$model_list$solver + ) + TRUE + }, + error = function(e) { + diagnostic <<- conditionMessage(e) + FALSE + } + ) + + if (!quiet) { + if (ok) { + cli::cli_inform(c("v" = "Runtime validation passed.")) + } else { + cli::cli_warn("Runtime validation failed.") + cat(diagnostic, "\n", sep = "") + } + } + + invisible(list( + ok = ok, + stage = if (ok) "validated" else "runtime", + dsl = model_source, + numbered_dsl = numbered_dsl, + diagnostic = if (ok) NULL else diagnostic + )) }, #' @description - #' Compile the model to a binary file. + #' Validate the model DSL. #' @details - #' This method write the model to a Rust file in a temporary path, - #' updates the `binary_path` field for the model, and compiles that - #' file to a binary file that can be used for fitting or simulation. + #' This method lowers the model to pharmsol DSL and validates that source + #' with the runtime compiler used for fitting and simulation. #' @param quiet Logical, if TRUE, suppresses messages during compilation. - #' compile = function(quiet = FALSE) { - if (!is.null(self$binary_path) && file.exists(self$binary_path)) { - # model is compiled - return(invisible(NULL)) - } - - model_path <- file.path(tempdir(), "model.rs") - private$write_model_to_rust(model_path) - output_path <- tempfile(pattern = "model_", fileext = ".pmx") if (!quiet) cli::cli_inform(c("i" = "Compiling model...")) - # path inside Pmetrics package - template_path <- resolve_template_path() tryCatch( { - compile_model(model_path, output_path, private$get_primary(), template_path, kind = tolower(self$model_list$type)) - self$binary_path <- output_path + validate_model_source( + model_source = self$dsl(), + kind = tolower(self$model_list$type), + solver = self$model_list$solver + ) }, error = function(e) { cli::cli_abort( @@ -1704,7 +1742,7 @@ PM_model <- R6::R6Class( "\n err = list(\n", purrr::map_chr((arg_list$err), \(i) { sprintf( - " %s(%i, c(%.1f, %.1f, %.1f, %.1f)%s)", + " %s(%.15g, c(%.1f, %.1f, %.1f, %.1f)%s)", i$type, i$initial, ifelse(length(i$coeff) >= 1, i$coeff[1], 0), @@ -1920,7 +1958,8 @@ PM_model <- R6::R6Class( coeff_fxns <- err[-1] |> purrr::imap(\(x, idx) { - glue::glue("{err_type}({gamlam_value}, c({x}), {const_coeff[{idx}]})") + fixed_error <- const_gamlam || const_coeff[[idx]] + glue::glue("{err_type}({gamlam_value}, c({x}), {fixed_error})") }) |> unlist() @@ -1931,64 +1970,11 @@ PM_model <- R6::R6Class( return(arg_list) }, # end R6fromFile - write_model_to_rust = function(file_path = "main.rs") { - # Check if model_list is not NULL - if (is.null(self$model_list)) { - cli::cli_abort(c("x" = "Model list is empty.", "i" = "Please provide a valid model list.")) - } - - if (self$model_list$type == "ODE") { - placeholders <- c("eqn", "lag", "fa", "ini", "out") - base <- paste0( - "{\n", - " #[allow(unused_assignments)]\n", - " #[allow(unused_mut)]\n", - " #[allow(unused_variables)]\n", - " fn build_eqn() -> impl Equation {\n", - " ode! {\n", - " diffeq: ,\n", - " lag: ,\n", - " fa: ,\n", - " init: ,\n", - " out: ,\n", - " }", - if (!is.null(self$model_list$solver_rust)) { - paste0("\n .with_solver(", self$model_list$solver_rust, ")") - } else { - "" - }, - "\n }\n", - " build_eqn()\n", - "}" - ) - } else if (self$model_list$type == "Analytical") { - placeholders <- c("eqn", "lag", "fa", "ini", "out", "n_eqn_slots", "n_drug_slots", "n_out_slots") - base <- paste0( - "{\n", - " #[allow(unused_assignments)]\n", - " #[allow(unused_mut)]\n", - " #[allow(unused_variables)]\n", - " fn build_eqn() -> impl Equation {\n", - " equation::Analytical::new(\n", - paste("<", placeholders[1:5], ">", sep = "", collapse = ",\n "), - "\n )\n", - " .with_nstates()\n", - " .with_ndrugs()\n", - " .with_nout()\n", - " }\n", - " build_eqn()\n", - "}" - ) - } else { - cli::cli_abort(c("x" = "Invalid model type.", "i" = "Please provide a valid model type.")) - } - - - # Replace placeholders in the base string with actual values from model_list - base <- placeholders |> - purrr::reduce(\(x, y) stringr::str_replace(x, stringr::str_c("<", y, ">"), as.character(self$model_list[[y]])), .init = base) - # Write the model to a file - writeLines(base, file_path) + build_model_dsl = function() { + pm_model_to_dsl_source(self) + }, + write_model_to_dsl = function(file_path = "model.dsl") { + writeLines(private$build_model_dsl(), file_path) }, from_file = function(file_path) { self$model_list <- private$makeR6model(model_filename) @@ -1999,28 +1985,6 @@ PM_model <- R6::R6Class( ) # end private ) # end R6Class PM_model -ode_solver_to_rust <- function(solver) { - if (!is.character(solver) || length(solver) != 1) { - cli::cli_abort(c( - "x" = "{.arg solver} must be a single character value.", - "i" = "Supported values are 'BDF', 'TRBDF2', 'ESDIRK34', and 'TSIT45'." - )) - } - - solver <- tolower(solver) - - switch(solver, - bdf = "OdeSolver::Bdf", - trbdf2 = "OdeSolver::Sdirk(SdirkTableau::TrBdf2)", - esdirk34 = "OdeSolver::Sdirk(SdirkTableau::Esdirk34)", - tsit45 = "OdeSolver::ExplicitRk(ExplicitRkTableau::Tsit45)", - cli::cli_abort(c( - "x" = "Unsupported {.arg solver} value: {.val {solver}}.", - "i" = "Supported values are 'BDF', 'TRBDF2', 'ESDIRK34', and 'TSIT45'." - )) - ) -} - ##### These functions create various model components #' @title Additive error model diff --git a/R/PM_op.R b/R/PM_op.R index 296233f35..52921ebd1 100755 --- a/R/PM_op.R +++ b/R/PM_op.R @@ -128,9 +128,9 @@ PM_op <- R6::R6Class( ), # end public private = list( make = function(data, path) { - if (file.exists(file.path(path, "pred.csv"))) { + if (file.exists(file.path(path, "predictions.csv"))) { op_raw <- readr::read_csv( - file = file.path(path, "pred.csv"), + file = file.path(path, "predictions.csv"), col_types = list( time = readr::col_double(), outeq = readr::col_integer(), @@ -156,7 +156,7 @@ PM_op <- R6::R6Class( } else { cli::cli_warn(c( "!" = "Unable to generate obs-pred information.", - "i" = "{.file {file.path(path, 'pred.csv')}} does not exist, and result does not have valid {.code PM_op} object ." + "i" = "{.file {file.path(path, 'predictions.csv')}} does not exist, and result does not have valid {.code PM_op} object ." )) return(NULL) } @@ -338,22 +338,21 @@ PM_op <- R6::R6Class( #' } #' @family PMplots plot.PM_op <- function( - x, - line = list(lm = NULL, loess = NULL, ref = NULL), - marker = TRUE, - resid = FALSE, - icen = "median", pred.type = "post", outeq = 1, block, - include, exclude, - mult = 1, - legend, - log = FALSE, - grid = TRUE, - xlab, ylab, - title, - stats = TRUE, - print = TRUE, - xlim, ylim, ... -) { + x, + line = list(lm = NULL, loess = NULL, ref = NULL), + marker = TRUE, + resid = FALSE, + icen = "median", pred.type = "post", outeq = 1, block, + include, exclude, + mult = 1, + legend, + log = FALSE, + grid = TRUE, + xlab, ylab, + title, + stats = TRUE, + print = TRUE, + xlim, ylim, ...) { if (inherits(x, "PM_op")) { x <- x$data } @@ -803,10 +802,9 @@ plot.PM_op_data <- function(x, ...) { #' @export summary.PM_op <- function( - object, digits = max(3, getOption("digits") - 3), - pred.type = "post", icen = "median", - outeq = 1, ... -) { + object, digits = max(3, getOption("digits") - 3), + pred.type = "post", icen = "median", + outeq = 1, ...) { argList <- list(...) if ("type" %in% names(argList)) { cli::cli_inform(c("i" = "{.code type} has been updated to {.code pred.type}.\fPlease update your script.")) diff --git a/R/PM_parse.R b/R/PM_parse.R index b416b18e9..86ebeab1b 100755 --- a/R/PM_parse.R +++ b/R/PM_parse.R @@ -36,7 +36,7 @@ PM_parse <- function(path = ".", fit = "fit.rds", write = TRUE) { cli::cli_abort(c("x" = "The directory {.path {path}} does not exist.")) } - # assumes pred.csv and settings.json are in wd + # assumes predictions.csv and settings.json are in wd op <- rlang::try_fetch(PM_op$new(path = path), error = function(e) { cli::cli_warn("Unable to create {.cls PM_op} object", parent = e) @@ -52,7 +52,7 @@ PM_parse <- function(path = ".", fit = "fit.rds", write = TRUE) { } ) - # assumes cycles.csv, and settings.json are in wd + # assumes iterations.csv and settings.json are in wd cycle <- rlang::try_fetch(PM_cycle$new(path = path), error = function(e) { cli::cli_warn("Unable to create {.cls PM_cycle} object", parent = e) @@ -60,7 +60,7 @@ PM_parse <- function(path = ".", fit = "fit.rds", write = TRUE) { } ) - # assumes pred.csv is in wd + # assumes predictions.csv is in wd pop <- rlang::try_fetch(PM_pop$new(path = path), error = function(e) { cli::cli_warn("Unable to create {.cls PM_pop} object", parent = e) @@ -68,7 +68,7 @@ PM_parse <- function(path = ".", fit = "fit.rds", write = TRUE) { } ) - # assumes pred.csv is in wd + # assumes predictions.csv is in wd post <- rlang::try_fetch(PM_post$new(path = path), error = function(e) { cli::cli_warn("Unable to create {.cls PM_post} object", parent = e) @@ -84,16 +84,15 @@ PM_parse <- function(path = ".", fit = "fit.rds", write = TRUE) { ) config <- rlang::try_fetch(jsonlite::fromJSON(suppressWarnings(readLines(file.path(path, "settings.json"), warn = FALSE))), - error = function(e) { - cli::cli_warn(c("!" = "Unable to read {.file {file.path(path, 'settings.json')}}")) - return(NULL) - } - ) + error = function(e) { + cli::cli_warn(c("!" = "Unable to read {.file {file.path(path, 'settings.json')}}")) + return(NULL) + } + ) core <- list( data = fit_object$data, model = fit_object$model, - model_binary_path = if (!is.null(fit_object) && inherits(fit_object$model, "PM_model")) fit_object$model$binary_path else NULL, op = op, cov = cov, post = post, @@ -104,7 +103,9 @@ PM_parse <- function(path = ".", fit = "fit.rds", write = TRUE) { config = config, sys = { info <- as.list(Sys.info()) - info |> keep(names(info) %in% c("sysname", "machine")) |> paste(collapse = " ") + info |> + keep(names(info) %in% c("sysname", "machine")) |> + paste(collapse = " ") } ) diff --git a/R/PM_pop.R b/R/PM_pop.R index 44f6dbe2e..aa88883e7 100755 --- a/R/PM_pop.R +++ b/R/PM_pop.R @@ -91,9 +91,9 @@ PM_pop <- R6::R6Class( ), # end public private = list( make = function(data, path) { - if (file.exists(file.path(path, "pred.csv"))) { + if (file.exists(file.path(path, "predictions.csv"))) { op_raw <- readr::read_csv( - file = file.path(path, "pred.csv"), + file = file.path(path, "predictions.csv"), col_types = list( time = readr::col_double(), outeq = readr::col_integer(), @@ -112,7 +112,7 @@ PM_pop <- R6::R6Class( } else { cli::cli_warn(c( "!" = "Unable to generate pop pred information.", - "i" = "{.file {file.path(path, 'pred.csv')}} does not exist, and result does not have valid {.code PM_pop} object." + "i" = "{.file {file.path(path, 'predictions.csv')}} does not exist, and result does not have valid {.code PM_pop} object." )) return(NULL) } @@ -223,26 +223,25 @@ PM_pop <- R6::R6Class( #' @family PMplots plot.PM_pop <- function( - x, - include = NULL, - exclude = NULL, - line = list(join = TRUE), - marker = FALSE, - out_names = NULL, - mult = 1, - icen = "median", - outeq = 1, - block = 1, - overlay = TRUE, - legend = FALSE, - log = FALSE, - grid = FALSE, - xlab = "Time", - ylab = "Output", - title = "", - xlim, ylim, - print = TRUE, ... -) { + x, + include = NULL, + exclude = NULL, + line = list(join = TRUE), + marker = FALSE, + out_names = NULL, + mult = 1, + icen = "median", + outeq = 1, + block = 1, + overlay = TRUE, + legend = FALSE, + log = FALSE, + grid = FALSE, + xlab = "Time", + ylab = "Output", + title = "", + xlim, ylim, + print = TRUE, ...) { # Plot parameters --------------------------------------------------------- x <- if (inherits(x, "PM_pop")) { @@ -491,10 +490,9 @@ plot.PM_pop <- function( #' @export summary.PM_pop <- function( - object, digits = max(3, getOption("digits") - 3), - icen = "median", - outeq = 1, ... -) { + object, digits = max(3, getOption("digits") - 3), + icen = "median", + outeq = 1, ...) { sumWrk <- function(data) { sumstat <- matrix(NA, nrow = 7, ncol = 2, dimnames = list(c("Min", "25%", "Median", "75%", "Max", "Mean", "SD"), c("Time", "Pred"))) # min diff --git a/R/PM_post.R b/R/PM_post.R index eb8fb5b85..b4f2b11c2 100755 --- a/R/PM_post.R +++ b/R/PM_post.R @@ -92,9 +92,9 @@ PM_post <- R6::R6Class( ), # end public private = list( make = function(data, path) { - if (file.exists(file.path(path, "pred.csv"))) { + if (file.exists(file.path(path, "predictions.csv"))) { op_raw <- readr::read_csv( - file = file.path(path, "pred.csv"), + file = file.path(path, "predictions.csv"), col_types = list( time = readr::col_double(), outeq = readr::col_integer(), @@ -113,7 +113,7 @@ PM_post <- R6::R6Class( } else { cli::cli_warn(c( "!" = "Unable to generate post pred information.", - "i" = "{.file {file.path(path, 'pred.csv')}} does not exist, and result does not have valid {.code PM_post} object." + "i" = "{.file {file.path(path, 'predictions.csv')}} does not exist, and result does not have valid {.code PM_post} object." )) return(NULL) } @@ -226,26 +226,25 @@ PM_post <- R6::R6Class( #' @family PMplots plot.PM_post <- function( - x, - include = NULL, - exclude = NULL, - line = list(join = TRUE), - marker = FALSE, - out_names = NULL, - mult = 1, - icen = "median", - outeq = 1, - block = 1, - overlay = TRUE, - legend = FALSE, - log = FALSE, - grid = FALSE, - xlab = "Time", - ylab = "Output", - title = "", - print = TRUE, - xlim, ylim, ... -) { + x, + include = NULL, + exclude = NULL, + line = list(join = TRUE), + marker = FALSE, + out_names = NULL, + mult = 1, + icen = "median", + outeq = 1, + block = 1, + overlay = TRUE, + legend = FALSE, + log = FALSE, + grid = FALSE, + xlab = "Time", + ylab = "Output", + title = "", + print = TRUE, + xlim, ylim, ...) { # Plot parameters --------------------------------------------------------- x <- if (inherits(x, "PM_post")) { @@ -496,10 +495,9 @@ plot.PM_post <- function( #' @export summary.PM_post <- function( - object, digits = max(3, getOption("digits") - 3), - icen = "median", - outeq = 1, ... -) { + object, digits = max(3, getOption("digits") - 3), + icen = "median", + outeq = 1, ...) { sumWrk <- function(data) { sumstat <- matrix(NA, nrow = 7, ncol = 2, dimnames = list(c("Min", "25%", "Median", "75%", "Max", "Mean", "SD"), c("Time", "Pred"))) # min diff --git a/R/PM_result.R b/R/PM_result.R index 58de22af2..3b8ea3640 100755 --- a/R/PM_result.R +++ b/R/PM_result.R @@ -367,7 +367,7 @@ PM_load <- function(run, path = ".", file = "PMout.Rdata") { # internal function output2List <- function(Out) { result <- list() - for (i in 1:length(Out)) { + for (i in seq_along(Out)) { aux_list <- list(Out[[i]]) names(aux_list) <- names(Out)[i] result <- append(result, aux_list) @@ -392,52 +392,6 @@ PM_load <- function(run, path = ".", file = "PMout.Rdata") { result <- output2List(Out = get(load(found))) rebuild <- PM_result$new(result, path = dirname(found), quiet = TRUE) - inputs_dir <- normalizePath(file.path(dirname(found), "..", "inputs"), mustWork = FALSE) - inputs_binaries <- if (dir.exists(inputs_dir)) { - list.files(inputs_dir, pattern = "\\.pmx$", full.names = TRUE) - } else { - character(0) - } - - if (inherits(rebuild$model, "PM_model")) { - is_valid_path <- function(x) { - is.character(x) && length(x) == 1 && !is.na(x) && nzchar(x) - } - - # Prefer the binary path stored in the saved result (model_binary_path); - # fall back to the model's current binary_path field if not available. - target_binary_path <- result$model_binary_path - if (!is_valid_path(target_binary_path)) { - target_binary_path <- rebuild$model$binary_path - } - - if (length(inputs_binaries) > 0) { - source_binary <- inputs_binaries[1] - if (is_valid_path(target_binary_path)) { - preferred_source <- normalizePath(file.path(inputs_dir, basename(target_binary_path)), mustWork = FALSE) - if (file.exists(preferred_source)) { - source_binary <- preferred_source - } - } - - if (is_valid_path(target_binary_path)) { - target_binary_path <- normalizePath(target_binary_path, mustWork = FALSE) - copied <- tryCatch( - { - dir.create(dirname(target_binary_path), recursive = TRUE, showWarnings = FALSE) - isTRUE(file.copy(source_binary, target_binary_path, overwrite = TRUE)) - }, - error = function(e) FALSE - ) - rebuild$model$binary_path <- if (copied) target_binary_path else source_binary - } else { - rebuild$model$binary_path <- source_binary - } - } else { - rebuild$model$binary_path <- NULL - } - } - return(rebuild) } else { cli::cli_abort(c("x" = "No Pmetrics output file found in {.path {path}}.")) @@ -459,7 +413,7 @@ update <- function(res, found) { n_cyc <- nrow(dat$mean) n_out <- max(res$op$outeq) dat$gamlam <- dat$gamlam |> select(starts_with("add") | starts_with("prop")) - if (ncol(gamlam) == 1 & n_out > 1) { + if (ncol(gamlam) == 1 && n_out > 1) { gamlam <- cbind(gamlam, replicate((n_out - 1), gamlam[, 1])) } gamlam <- gamlam |> diff --git a/R/PM_sim.R b/R/PM_sim.R index e1a5b6474..59d3f2b11 100755 --- a/R/PM_sim.R +++ b/R/PM_sim.R @@ -62,7 +62,7 @@ PM_sim <- R6::R6Class( #' and an `id` column to identify the template in the `data` source. #' Again, this can be useful as a check against the original covariance in `poppar`. data = NULL, - + #' @description #' This function simulates outputs from given inputs and a model. #' It can be called directly @@ -553,341 +553,347 @@ PM_sim <- R6::R6Class( #' #' } initialize = function(poppar, model, data, - limits = NULL, - split = NULL, - include = NULL, exclude = NULL, - nsim = 1000, - predInt = 0, - covariate = NULL, - usePost = FALSE, - seed = -17, - noise = NULL, - makecsv = NULL, - quiet = FALSE, - ...) { - # handle deprecated arguments - - dots <- list(...) - old_noise <- c("obsNoise", "obsTimeNoise", "doseNoise", "doseTimeNoise") %in% names(dots) - msg <- NULL - - if (any(old_noise)) { - cli::cli_abort(c( - "x" = "{.arg {c('obsNoise', 'obsTimeNoise', 'doseNoise', 'doseTimeNoise')[old_noise]}} {?is/are} deprecated.", - "i" = "Instead, use {.arg noise}. See {.help PM_sim}." - )) + limits = NULL, + split = NULL, + include = NULL, exclude = NULL, + nsim = 1000, + predInt = 0, + covariate = NULL, + usePost = FALSE, + seed = -17, + noise = NULL, + makecsv = NULL, + quiet = FALSE, + ...) { + # handle deprecated arguments + + dots <- list(...) + old_noise <- c("obsNoise", "obsTimeNoise", "doseNoise", "doseTimeNoise") %in% names(dots) + msg <- NULL + + if (any(old_noise)) { + cli::cli_abort(c( + "x" = "{.arg {c('obsNoise', 'obsTimeNoise', 'doseNoise', 'doseTimeNoise')[old_noise]}} {?is/are} deprecated.", + "i" = "Instead, use {.arg noise}. See {.help PM_sim}." + )) + } + + # check to make sure any arguments in ... are not misspelled or otherwise unrecognized + allArgs <- formals(PM_sim$public_methods$initialize) |> names() + # exclude the variadic placeholder from validation + allArgs <- setdiff(allArgs, "...") + dotArgs <- names(dots) + # only validate truly named arguments (non-NA, non-empty names) + dotArgs <- dotArgs[!is.null(dotArgs) & !is.na(dotArgs) & nzchar(dotArgs)] + unrecog <- setdiff(dotArgs, allArgs) + if (length(unrecog) > 0) { + cli::cli_abort(c( + "x" = "The following argument{?s} {?is/are} not recognized: {.val {unrecog}}.", + "i" = "Check for case errors or misspellings. Refer to {.help PM_sim} for a list of valid arguments." + )) + } + + + if (missing(poppar)) { + cli::cli_abort(c( + "x" = "The poppar argument is required.", + "i" = "See ?PM_sim for help." + )) + } + + useTheta <- FALSE # default, unless poppar is theta.csv format + + refresh_model <- function(model) { + if (inherits(model, "PM_model")) { + return(PM_model$new(x = model, compile = FALSE)) + } + + PM_model$new(model, compile = FALSE) + } + + # CASE 1 - poppar is PM_result + + if (inherits(poppar, "PM_result")) { + case <- 1 + final <- poppar$final$data # PM_final_data + msg <- c(msg, "Prior obtained from {.arg PM_result}.") + if (missing(model)) { + model <- refresh_model(poppar$model) + msg <- c(msg, "Model obtained from {.arg PM_result}.") + } else { + model <- refresh_model(model) + } + + if (missing(data)) { + data <- poppar$data + msg <- c(msg, "Data obtained from {.arg PM_result}.") + } else { + data <- PM_data$new(data, quiet = quiet) } - - # check to make sure any arguments in ... are not misspelled or otherwise unrecognized - allArgs <- formals(PM_sim$public_methods$initialize) |> names() - # exclude the variadic placeholder from validation - allArgs <- setdiff(allArgs, "...") - dotArgs <- names(dots) - # only validate truly named arguments (non-NA, non-empty names) - dotArgs <- dotArgs[!is.null(dotArgs) & !is.na(dotArgs) & nzchar(dotArgs)] - unrecog <- setdiff(dotArgs, allArgs) - if (length(unrecog) > 0) { + + # CASE 2 - poppar is PM_final + } else if (inherits(poppar, "PM_final")) { + case <- 2 + final <- poppar$data # PM_final_data + + + # CASE 3 - poppar is PM_final_data + } else if (inherits(poppar, "PM_final_data")) { + case <- 3 + final <- poppar # PM_final_data + + + ### The following cases are for handling old simulation objects + + + # CASE 4 - poppar is old PMsim + } else if (inherits(poppar, "PMsim")) { # from SIMparse as single + private$populate(poppar, type = "sim") + return(self) # end, we have loaded a prior sim + + # CASE 5 - poppar is old PM_simlist + } else if (inherits(poppar, "PM_simlist")) { # from SIMparse as list + private$populate(poppar, type = "simlist") + return(self) # end, we have loaded a prior sim + + # CASE 6 - poppar is PM_sim from R6 + } else if (inherits(poppar, "PM_sim")) { # from R6 + private$populate(poppar, type = "R6sim") + return(self) # end, we have loaded a prior sim + + ### This creates a new simulation from a manual list + + # CASE 7 - poppar is manual list + } else if (inherits(poppar, "list")) { # PM_final and PM_sim are lists, so needs to be after those for manual list + case <- 7 + + # parse poppar list + poppar_elements <- names(poppar) |> purrr::discard(~ .x == "wt") + + if (length(poppar_elements) == 2 && all(c("mean", "sd") %in% poppar_elements)) { # mean and SD only + poppar$cov <- diag(poppar$sd^2) # make covariance matrix + } else if (length(poppar_elements) == 2 && all(c("mean", "cov") %in% poppar_elements)) { # mean, covariance/correlation matrix, and weights + poppar <- poppar # all good + } else if (length(poppar_elements) == 3 && all(c("mean", "sd", "cor") %in% poppar_elements)) { # mean, SD, covariance/correlation matrix, and weights + poppar$cov <- cor2cov(poppar$cor, poppar$sd) # convert correlation matrix to covariance matrix + } else { cli::cli_abort(c( - "x" = "The following argument{?s} {?is/are} not recognized: {.val {unrecog}}.", - "i" = "Check for case errors or misspellings. Refer to {.help PM_sim} for a list of valid arguments." + "x" = "{.arg poppar} is malformed with elements {.val {poppar_elements}}.", + "i" = "See ?PM_sim for help on constructing {.arg poppar}." )) } - - - if (missing(poppar)) { + + # add missing wt if needed + if (!"wt" %in% names(poppar)) { + poppar$wt <- 1 # default weight + } + + # check to ensure wt and mean are aligned + if (!all(map_lgl(poppar$mean, \(x) length(x) == length(poppar$wt)))) { cli::cli_abort(c( - "x" = "The poppar argument is required.", + "x" = "The length of each vector in {.arg poppar$mean} must be equal to the length of {.arg poppar$wt}.", "i" = "See ?PM_sim for help." )) } - - useTheta <- FALSE # default, unless poppar is theta.csv format - - # CASE 1 - poppar is PM_result - - if (inherits(poppar, "PM_result")) { - case <- 1 - final <- poppar$final$data # PM_final_data - msg <- c(msg, "Prior obtained from {.arg PM_result}.") - if (missing(model)) { - model <- poppar$model - msg <- c(msg, "Model obtained from {.arg PM_result}.") - } else { - model <- PM_model$new(model, compile = FALSE) # compile later - } - - if (missing(data)) { - data <- poppar$data - msg <- c(msg, "Data obtained from {.arg PM_result}.") - } else { - data <- PM_data$new(data, quiet = quiet) - } - - # CASE 2 - poppar is PM_final - } else if (inherits(poppar, "PM_final")) { - case <- 2 - final <- poppar$data # PM_final_data - - - # CASE 3 - poppar is PM_final_data - } else if (inherits(poppar, "PM_final_data")) { - case <- 3 - final <- poppar # PM_final_data - - - ### The following cases are for handling old simulation objects - - - # CASE 4 - poppar is old PMsim - } else if (inherits(poppar, "PMsim")) { # from SIMparse as single - private$populate(poppar, type = "sim") - return(self) # end, we have loaded a prior sim - - # CASE 5 - poppar is old PM_simlist - } else if (inherits(poppar, "PM_simlist")) { # from SIMparse as list - private$populate(poppar, type = "simlist") - return(self) # end, we have loaded a prior sim - - # CASE 6 - poppar is PM_sim from R6 - } else if (inherits(poppar, "PM_sim")) { # from R6 - private$populate(poppar, type = "R6sim") - return(self) # end, we have loaded a prior sim - - ### This creates a new simulation from a manual list - - # CASE 7 - poppar is manual list - } else if (inherits(poppar, "list")) { # PM_final and PM_sim are lists, so needs to be after those for manual list - case <- 7 - - # parse poppar list - poppar_elements <- names(poppar) |> purrr::discard(~ .x == "wt") - - if (length(poppar_elements) == 2 && all(c("mean", "sd") %in% poppar_elements)) { # mean and SD only - poppar$cov <- diag(poppar$sd^2) # make covariance matrix - } else if (length(poppar_elements) == 2 && all(c("mean", "cov") %in% poppar_elements)) { # mean, covariance/correlation matrix, and weights - poppar <- poppar # all good - } else if (length(poppar_elements) == 3 && all(c("mean", "sd", "cor") %in% poppar_elements)) { # mean, SD, covariance/correlation matrix, and weights - poppar$cov <- cor2cov(poppar$cor, poppar$sd) # convert correlation matrix to covariance matrix - } else { - cli::cli_abort(c( - "x" = "{.arg poppar} is malformed with elements {.val {poppar_elements}}.", - "i" = "See ?PM_sim for help on constructing {.arg poppar}." - )) - } - - # add missing wt if needed - if (!"wt" %in% names(poppar)) { - poppar$wt <- 1 # default weight - } - - # check to ensure wt and mean are aligned - if (!all(map_lgl(poppar$mean, \(x) length(x) == length(poppar$wt)))) { + + # get final into right format + final <- list( + popWeight = poppar$wt, + popMean = tibble::as_tibble(do.call(cbind, poppar$mean)), + popCov = data.frame(poppar$cov) + ) + + # not returning because going on to simulate below + + ### This is for loading a saved simulation from file + } else if (inherits(poppar, "data.frame")) { # poppar is in the form of theta.csv + poppar$prob <- 1 / nrow(poppar) + final <- list(popPoints = poppar) + useTheta <- TRUE + case <- 8 + + # CASE 9 - last option, poppar is filename + } else { + if (file.exists(poppar)) { + if (grepl("rds$", poppar, perl = TRUE)) { # poppar is rds filename + sim <- readRDS(poppar) + } else { # poppar is a simout.txt name cli::cli_abort(c( - "x" = "The length of each vector in {.arg poppar$mean} must be equal to the length of {.arg poppar$wt}.", - "i" = "See ?PM_sim for help." + "x" = "{poppar} is not a compatible file.", + "i" = "Please remake your simulation." )) } - - # get final into right format - final <- list( - popWeight = poppar$wt, - popMean = tibble::as_tibble(do.call(cbind, poppar$mean)), - popCov = data.frame(poppar$cov) - ) - - # not returning because going on to simulate below - - ### This is for loading a saved simulation from file - } else if (inherits(poppar, "data.frame")) { # poppar is in the form of theta.csv - poppar$prob <- 1 / nrow(poppar) - final <- list(popPoints = poppar) - useTheta <- TRUE - case <- 8 - - # CASE 9 - last option, poppar is filename - } else { - if (file.exists(poppar)) { - if (grepl("rds$", poppar, perl = TRUE)) { # poppar is rds filename - sim <- readRDS(poppar) - } else { # poppar is a simout.txt name - cli::cli_abort(c( - "x" = "{poppar} is not a compatible file.", - "i" = "Please remake your simulation." - )) - } - # now populate - if (inherits(sim, c("PMsim", "PM_sim_data"))) { - private$populate(sim, type = "sim") - } else if (inherits(sim, "PM_simlist")) { - private$populate(sim, type = "simlist") - } else if (inherits(sim, "PM_sim")) { - private$populate(sim, type = "R6sim") - } else { - cli::cli_abort(c("x" = "{poppar} is not a Pmetrics simulation.")) - } - return(self) # end, we have loaded a prior sim + # now populate + if (inherits(sim, c("PMsim", "PM_sim_data"))) { + private$populate(sim, type = "sim") + } else if (inherits(sim, "PM_simlist")) { + private$populate(sim, type = "simlist") + } else if (inherits(sim, "PM_sim")) { + private$populate(sim, type = "R6sim") } else { - cli::cli_abort(c("x" = "{poppar} does not exist in the current working directory.")) + cli::cli_abort(c("x" = "{poppar} is not a Pmetrics simulation.")) } - } # end if poppar is filename - - # If we reach this point, we are creating a new simulation - - # check model and data - if (case %in% c(2, 3, 7, 8)) { # need model and data if not from PM_result - if (missing(model)) { - model <- "model.txt" - } # try the default - if (!inherits(model, "PM_model")) { - model <- PM_model$new(model, compile = FALSE) - } # compile later - - if (missing(data)) { - data <- "data.csv" - } # try the default - if (!inherits(data, "PM_data")) { - data <- PM_data$new(data, quiet = quiet) - } # will abort if can't make PM_data + return(self) # end, we have loaded a prior sim + } else { + cli::cli_abort(c("x" = "{poppar} does not exist in the current working directory.")) } - - - # set default values - - if (is.null(split)) { - if (inherits(poppar, "NPAG")) { - split <- TRUE - } else { - split <- FALSE - } + } # end if poppar is filename + + # If we reach this point, we are creating a new simulation + + # check model and data + if (case %in% c(2, 3, 7, 8)) { # need model and data if not from PM_result + if (missing(model)) { + model <- "model.txt" + } # try the default + model <- refresh_model(model) + + if (missing(data)) { + data <- "data.csv" + } # try the default + if (!inherits(data, "PM_data")) { + data <- PM_data$new(data, quiet = quiet) + } # will abort if can't make PM_data + } + + + # set default values + + if (is.null(split)) { + if (inherits(poppar, "NPAG")) { + split <- TRUE + } else { + split <- FALSE + } + } + + if (!is.null(covariate)) { + # check to make sure covariate argument is list (cov, mean, sd, limits, fix) + if (!inherits(covariate, "list")) { + cli::cli_abort(c( + "x" = "The covariate argument must be a list.", + "i" = "See ?PM_sim for help." + )) } - - if (!is.null(covariate)) { - # check to make sure covariate argument is list (cov, mean, sd, limits, fix) - if (!inherits(covariate, "list")) { + + # check to ensure first element is correct + if (length(covariate) == 0 || !"cov" %in% names(covariate)) { + if (inherits(poppar, "PM_result")) { + covariate$cov <- poppar$cov + msg <- c(msg, "Covariate statistics obtained from {.code PM_result$cov}.") + } else { cli::cli_abort(c( - "x" = "The covariate argument must be a list.", + "x" = "The {.arg covariate$cov} argument must be supplied if `poppar` is not a {.fn PM_result} object.", "i" = "See ?PM_sim for help." )) } - - # check to ensure first element is correct - if (length(covariate) == 0 || !"cov" %in% names(covariate)) { - if (inherits(poppar, "PM_result")) { - covariate$cov <- poppar$cov - msg <- c(msg, "Covariate statistics obtained from {.code PM_result$cov}.") - } else { - cli::cli_abort(c( - "x" = "The {.arg covariate$cov} argument must be supplied if `poppar` is not a {.fn PM_result} object.", - "i" = "See ?PM_sim for help." - )) - } - } - - - # check to make sure names are correct - covArgNames <- names(covariate) - badNames <- which(!covArgNames %in% c("cov", "mean", "sd", "limits", "fix")) - if (length(badNames) > 0) { + } + + + # check to make sure names are correct + covArgNames <- names(covariate) + badNames <- which(!covArgNames %in% c("cov", "mean", "sd", "limits", "fix")) + if (length(badNames) > 0) { + cli::cli_abort(c( + "x" = "The covariate argument must be a named list.", + "i" = "See ?PM_sim for help." + )) + } + + + # check if user specified covariate with posterior simulation? + if (usePost) { + cli::cli_abort(c( + "x" = "Conflicting simulation arguments.", + "i" = "You cannot simulate from posteriors while simulating covariates since each subject has their own covariates." + )) + } + + # check if covariate$cov is a matrix and if so, ensure $mean and $sd are present + if (inherits(covariate$cov, "matrix")) { + if (!("mean" %in% names(covariate))) { # covariate$cov is a matrix, with mean but no sd cli::cli_abort(c( - "x" = "The covariate argument must be a named list.", + "x" = "When {.arg covariate$cov} is a matrix, {.arg covariate$mean} must be present.", "i" = "See ?PM_sim for help." )) } - - - # check if user specified covariate with posterior simulation? - if (usePost) { - cli::cli_abort(c( - "x" = "Conflicting simulation arguments.", - "i" = "You cannot simulate from posteriors while simulating covariates since each subject has their own covariates." - )) - } - - # check if covariate$cov is a matrix and if so, ensure $mean and $sd are present - if (inherits(covariate$cov, "matrix")) { - if (!("mean" %in% names(covariate))) { # covariate$cov is a matrix, with mean but no sd - cli::cli_abort(c( - "x" = "When {.arg covariate$cov} is a matrix, {.arg covariate$mean} must be present.", - "i" = "See ?PM_sim for help." - )) - } - - if ("sd" %in% names(covariate)) { # covariate$cov is assumed to be a correlation matrix, since sd is present - covariate$cov <- cor2cov(covariate$cov, covariate$sd) # convert correlation matrix to covariance matrix - msg <- c(msg, "Covariate correlation matrix converted to covariance matrix.") - } + + if ("sd" %in% names(covariate)) { # covariate$cov is assumed to be a correlation matrix, since sd is present + covariate$cov <- cor2cov(covariate$cov, covariate$sd) # convert correlation matrix to covariance matrix + msg <- c(msg, "Covariate correlation matrix converted to covariance matrix.") } - - - # OK, all checks passed, so we can proceed with covariates - } # end if !is.null(covariate) - - # finally, call the simulator, which updates self$data - - private$SIMrun( - poppar = final, limits = limits, model = model, - data = data, split = split, - include = include, exclude = exclude, nsim = nsim, - predInt = predInt, - covariate = covariate, usePost = usePost, - seed = seed, ode = ode, - noise = noise, - makecsv = makecsv, outname = outname, clean = clean, - quiet = quiet, useTheta = useTheta, - nocheck = nocheck, overwrite = overwrite, msg = msg - ) - - return(self) - }, # end initialize - #' - #' @description - #' `r lifecycle::badge("stable")` - #' Save the current PM_sim object into a .rds file. - #' @param file_name Name of the file to be created, the default is PMsim.rds - save = function(file_name = "PMsim.rds") { - saveRDS(self, file_name) - }, - - #' @description - #' `r lifecycle::badge("stable")` - #' Plot `PM_sim` object. - #' @param at Index of the PM_sim object to be plotted. Default is 1. - #' result. - #' @param ... Arguments passed to [plot.PM_sim]. - plot = function(...) { - plot.PM_sim(self$data, ...) - }, - - #' @description - #' `r lifecycle::badge("stable")` - #' Calculate optimal sampling times - #' @param ... Additional parameters, refer to [PM_opt] - opt = function(...) { - PM_opt$new(self, ...) - }, - - #' @description - #' `r lifecycle::badge("stable")` - #' Estimates the Probability of Target Attaintment (PTA), based on the results - #' of the current Simulation. - #' @param ... Additional parameters, refer to [PM_pta]. - pta = function(...) { - PM_pta$new(self, ...) - }, - - #' @description - #' `r lifecycle::badge("stable")` - #' Calculates the AUC of the specified simulation - #' @param ... Arguments passed to [make_AUC]. - auc = function(...) { - rlang::try_fetch(make_AUC(self$data, ...), + } + + + # OK, all checks passed, so we can proceed with covariates + } # end if !is.null(covariate) + + # finally, call the simulator, which updates self$data + + private$SIMrun( + poppar = final, limits = limits, model = model, + data = data, split = split, + include = include, exclude = exclude, nsim = nsim, + predInt = predInt, + covariate = covariate, usePost = usePost, + seed = seed, ode = ode, + noise = noise, + makecsv = makecsv, outname = outname, clean = clean, + quiet = quiet, useTheta = useTheta, + nocheck = nocheck, overwrite = overwrite, msg = msg + ) + + return(self) + }, # end initialize + #' + #' @description + #' `r lifecycle::badge("stable")` + #' Save the current PM_sim object into a .rds file. + #' @param file_name Name of the file to be created, the default is PMsim.rds + save = function(file_name = "PMsim.rds") { + saveRDS(self, file_name) + }, + + #' @description + #' `r lifecycle::badge("stable")` + #' Plot `PM_sim` object. + #' @param at Index of the PM_sim object to be plotted. Default is 1. + #' result. + #' @param ... Arguments passed to [plot.PM_sim]. + plot = function(...) { + plot.PM_sim(self$data, ...) + }, + + #' @description + #' `r lifecycle::badge("stable")` + #' Calculate optimal sampling times + #' @param ... Additional parameters, refer to [PM_opt] + opt = function(...) { + PM_opt$new(self, ...) + }, + + #' @description + #' `r lifecycle::badge("stable")` + #' Estimates the Probability of Target Attaintment (PTA), based on the results + #' of the current Simulation. + #' @param ... Additional parameters, refer to [PM_pta]. + pta = function(...) { + PM_pta$new(self, ...) + }, + + #' @description + #' `r lifecycle::badge("stable")` + #' Calculates the AUC of the specified simulation + #' @param ... Arguments passed to [make_AUC]. + auc = function(...) { + rlang::try_fetch(make_AUC(self$data, ...), error = function(e) { cli::cli_warn("Unable to generate AUC.", parent = e) return(NULL) } ) }, - + #' @description #' `r lifecycle::badge("stable")` #' Summarize simulation @@ -895,7 +901,7 @@ PM_sim <- R6::R6Class( summary = function(...) { summary.PM_sim(self$data, ...) }, - + #' @description #' `r lifecycle::badge("deprecated")` #' Deprecated method to run a simulation. Replaced by `PM_sim$new()` to be @@ -905,7 +911,7 @@ PM_sim <- R6::R6Class( run = function(...) { lifecycle::deprecate_warn("2.1.0", "PM_sim$run()", details = "PM_sim$run() is deprecated. Please use PM_sim$new() instead.") }, - + #' @description #' `r lifecycle::badge("deprecated")` #' Deprecated method to load a prior simulation. Replaced by `PM_sim$new()` to be @@ -945,889 +951,887 @@ PM_sim <- R6::R6Class( private = list( # run the simulator SIMrun = function(poppar, limits, model, data, split, - include, exclude, nsim, predInt, - covariate, usePost, - seed, ode, - noise, - makecsv, outname, clean, quiet, useTheta, - nocheck, overwrite, msg) { - # DATA PROCESSING AND VALIDATION ------------------------------------------ - - - ###### POPPAR - - - npar <- if (useTheta) { - ncol(poppar$popPoints) - 1 + include, exclude, nsim, predInt, + covariate, usePost, + seed, ode, + noise, + makecsv, outname, clean, quiet, useTheta, + nocheck, overwrite, msg) { + # DATA PROCESSING AND VALIDATION ------------------------------------------ + + + ###### POPPAR + + + npar <- if (useTheta) { + ncol(poppar$popPoints) - 1 + } else { + length(poppar$popMean) + } + + + ###### MODEL + + # get information from model + arg_list <- model$arg_list + mod_list <- model$model_list + mod_npar <- length(mod_list$parameters) # number of random parameters + # mod_nfix <- sum(map(mod_list$pri, \(x) x$mode == "constant") |> unlist()) # number of constant parameters + # mod_nranfix <- sum(map(mod_list$pri, \(x) x$mode == "fixed") |> unlist()) # number of random but fixed parameters + mod_numeqt <- mod_list$n_out + mod_asserr <- map(mod_list$err, \(x) x$coeff) + + + ###### DATA + + # get information from data + template <- data$standard_data + + template_ncov <- getCov(template)$ncov # in PMutilities + template_covnames <- getCov(template)$covnames + template_numeqt <- max(template$outeq, na.rm = T) + + # handle include/exclude + template <- includeExclude(template, include, exclude) + toInclude <- unique(template$id) + nsub <- length(toInclude) + + ###### TEMPLATE / MODEL CROSSCHECKS + + if (nsub == 0) { + cli::cli_abort(c("x" = "No subjects to simulate.")) + } + + if (template_numeqt != mod_numeqt) { + cli::cli_abort(c("x" = "Number of output equations in model and data do not match.")) + } + + # if (!identical(sort(template_covnames), sort(mod_list$cov))) { + # cli::cli_abort(c("x" = "Covariate names in model and data do not match.")) + # } + + # POSTERIORS ---------------------------------------------- + + # check if simulating with the posteriors and if so, get all subject IDs + if (usePost) { + if (length(poppar$postMean) == 0) { + cli::cli_abort(c( + "x" = "Posterior parameters not found.", + "i" = "Please remake your model." + )) } else { - length(poppar$popMean) + postToUse <- unique(poppar$postMean$id) # get the id for each posterior mean + if (split) { + split <- FALSE + cli::cli_inform(c("i" = "{.arg split} set to {.val FALSE} for simulations from posteriors.")) + } + # now, filter by included IDs + postToUse <- postToUse[postToUse %in% toInclude] # subset the posteriors if applicable + if (length(postToUse) > 0 && length(postToUse) != nsub) { + cli::cli_abort(c("x" = "You have {length(postToUse)} posteriors and {nsub} selected subjects in the data file. These must be equal.")) + } } - - - ###### MODEL - - # get information from model - arg_list <- model$arg_list - mod_list <- model$model_list - mod_npar <- length(mod_list$parameters) # number of random parameters - # mod_nfix <- sum(map(mod_list$pri, \(x) x$mode == "constant") |> unlist()) # number of constant parameters - # mod_nranfix <- sum(map(mod_list$pri, \(x) x$mode == "fixed") |> unlist()) # number of random but fixed parameters - mod_numeqt <- mod_list$n_out - mod_asserr <- map(mod_list$err, \(x) x$coeff) - - - ###### DATA - - # get information from data - template <- data$standard_data - - template_ncov <- getCov(template)$ncov # in PMutilities - template_covnames <- getCov(template)$covnames - template_numeqt <- max(template$outeq, na.rm = T) - - # handle include/exclude - template <- includeExclude(template, include, exclude) - toInclude <- unique(template$id) - nsub <- length(toInclude) - - ###### TEMPLATE / MODEL CROSSCHECKS - - if (nsub == 0) { - cli::cli_abort(c("x" = "No subjects to simulate.")) + } else { + postToUse <- NULL + } + + + # SEED -------------------------------------------------------------------- + + + seed <- floor(seed) # ensure that seed is a vector of integers + + + # PARAMETER LIMITS -------------------------------------------------------- + if (all(is.null(limits))) { # limits are omitted altogether + parLimits <- tibble::tibble(par = 1:npar, min = rep(-Inf, npar), max = rep(Inf, npar)) + } else if (!any(is.na(limits)) & is.vector(limits)) { # no limit is NA and specified as vector of length 1 or 2 + # so first check to make sure poppar is a PM_final_data object + if (!inherits(poppar, "PM_final_data")) { + cli::cli_abort(c( + "x" = "Error in {.arg limits}.", + "i" = "{.arg poppar} must be a {.fn PM_final} or {.fn PM_result} object when multiplicative limits are specified." + )) } - - if (template_numeqt != mod_numeqt) { - cli::cli_abort(c("x" = "Number of output equations in model and data do not match.")) + orig_lim <- poppar$ab + if (length(limits) == 1) { # e.g. limits = 3, multiply upper... + limits <- c(1, limits) # ...and set lower multiplier to 1 } - - # if (!identical(sort(template_covnames), sort(mod_list$cov))) { - # cli::cli_abort(c("x" = "Covariate names in model and data do not match.")) - # } - - # POSTERIORS ---------------------------------------------- - - # check if simulating with the posteriors and if so, get all subject IDs - if (usePost) { - if (length(poppar$postMean) == 0) { - cli::cli_abort(c( - "x" = "Posterior parameters not found.", - "i" = "Please remake your model." - )) - } else { - postToUse <- unique(poppar$postMean$id) # get the id for each posterior mean - if (split) { - split <- FALSE - cli::cli_inform(c("i" = "{.arg split} set to {.val FALSE} for simulations from posteriors.")) - } - # now, filter by included IDs - postToUse <- postToUse[postToUse %in% toInclude] # subset the posteriors if applicable - if (length(postToUse) > 0 && length(postToUse) != nsub) { - cli::cli_abort(c("x" = "You have {length(postToUse)} posteriors and {nsub} selected subjects in the data file. These must be equal.")) - } - } + parLimits <- orig_lim |> mutate( + min = min * limits[1], + max = max * limits[2] + ) + } else if (length(limits) == 1 && is.na(limits)) { # limits specified as NA (use limits in model) + if (inherits(poppar, "PM_final_data")) { + parLimits <- poppar$ab } else { - postToUse <- NULL - } - - - # SEED -------------------------------------------------------------------- - - - seed <- floor(seed) # ensure that seed is a vector of integers - - - # PARAMETER LIMITS -------------------------------------------------------- - if (all(is.null(limits))) { # limits are omitted altogether - parLimits <- tibble::tibble(par = 1:npar, min = rep(-Inf, npar), max = rep(Inf, npar)) - } else if (!any(is.na(limits)) & is.vector(limits)) { # no limit is NA and specified as vector of length 1 or 2 - # so first check to make sure poppar is a PM_final_data object - if (!inherits(poppar, "PM_final_data")) { - cli::cli_abort(c( - "x" = "Error in {.arg limits}.", - "i" = "{.arg poppar} must be a {.fn PM_final} or {.fn PM_result} object when multiplicative limits are specified." - )) - } - orig_lim <- poppar$ab - if (length(limits) == 1) { # e.g. limits = 3, multiply upper... - limits <- c(1, limits) # ...and set lower multiplier to 1 - } - parLimits <- orig_lim |> mutate( - min = min * limits[1], - max = max * limits[2] + parLimits <- tibble::tibble( + par = model$model_list$parameters, + min = purrr::map_dbl(model$model_list$pri, ~ .x$min), + max = purrr::map_dbl(model$model_list$pri, ~ .x$max) ) - } else if (length(limits) == 1 && is.na(limits)) { # limits specified as NA (use limits in model) - if (inherits(poppar, "PM_final_data")) { - parLimits <- poppar$ab - } else { - parLimits <- tibble::tibble( - par = model$model_list$parameters, - min = purrr::map_dbl(model$model_list$pri, ~ .x$min), - max = purrr::map_dbl(model$model_list$pri, ~ .x$max) - ) - } - } else if (any(is.na(limits))) { # some NAs, causes error - cli::cli_abort(c( - "x" = "The {.arg limits} argument is malformed.", - "i" = "It must only contain {.code NA} as a single value, e.g. {.code limits = NA} to indicate that limits in the model should be used." - )) - } else { # limits specified as a list or data frame - parLimits <- tibble::as_tibble(limits) - if (!all(c("par", "min", "max") %in% names(parLimits))) { - cli::cli_abort(c("x" = "A manual {.arg limits} argument must be a data frame or list with elements {.val par}, {.val min}, and {.val max}.")) - } } - - if (!is.null(parLimits) && nrow(parLimits) != npar) { - cli::cli_abort(c("x" = "The number of rows in {.arg limits} must match the number of parameters in the model.")) + } else if (any(is.na(limits))) { # some NAs, causes error + cli::cli_abort(c( + "x" = "The {.arg limits} argument is malformed.", + "i" = "It must only contain {.code NA} as a single value, e.g. {.code limits = NA} to indicate that limits in the model should be used." + )) + } else { # limits specified as a list or data frame + parLimits <- tibble::as_tibble(limits) + if (!all(c("par", "min", "max") %in% names(parLimits))) { + cli::cli_abort(c("x" = "A manual {.arg limits} argument must be a data frame or list with elements {.val par}, {.val min}, and {.val max}.")) } - - - # COVARIATES ---------------------------------------------------- - - # if covariate is not null and simulating more than 1 new subject, - # augment prior with covariate correlations and modify model file - - simWithCov <- FALSE # default is no covariates - - if (!is.null(covariate)) { - simWithCov <- TRUE - - # get mean of each covariate and Bayesian posterior parameter - if (is.matrix(covariate$cov)) { # simulating covariates not in the data - if (inherits(poppar, "PM_final_data")) { - pars <- poppar$postMean # get the parameter values - } else { # manual poppar prior, so simulate arbitrary number of parameters - - poppar <- poppar |> + } + + if (!is.null(parLimits) && nrow(parLimits) != npar) { + cli::cli_abort(c("x" = "The number of rows in {.arg limits} must match the number of parameters in the model.")) + } + + + # COVARIATES ---------------------------------------------------- + + # if covariate is not null and simulating more than 1 new subject, + # augment prior with covariate correlations and modify model file + + simWithCov <- FALSE # default is no covariates + + if (!is.null(covariate)) { + simWithCov <- TRUE + + # get mean of each covariate and Bayesian posterior parameter + if (is.matrix(covariate$cov)) { # simulating covariates not in the data + if (inherits(poppar, "PM_final_data")) { + pars <- poppar$postMean # get the parameter values + } else { # manual poppar prior, so simulate arbitrary number of parameters + + poppar <- poppar |> purrr::set_names(c("wt", "popMean", "popCov")) |> c(list(popCor = cov2cor(.$popCov))) # keeps it consistent with PM_final_data - - weights <- poppar$wt - means <- bind_rows(poppar$popMean) - cov_matrix <- pos_def(poppar$popCov) # ensure covariance matrix is positive definite, function in PMutilities - if (length(cov_matrix) == 1 && cov_matrix == 1) { - return(invisible(NULL)) # quietly abort simulation - } else if (length(cov_matrix) == 1 && cov_matrix == -1) { - cli::cli_abort(c( - "x" = "Population parameter covariance matrix cannot be made positive definite.", - "i" = "Please check your covariance matrix." - )) - } - samples_per_mode <- stats::rmultinom(1, size = 50, prob = weights) - - # Generate samples for each mode - - pars <- map(1:length(weights), \(i) { - tryCatch(suppressWarnings( - PM_rtmvnorm( # in PMutilities - n = samples_per_mode[i], - mean = means[i, ], - sigma = cov_matrix, - lb = rep(0, ncol(means[i, ]))) - ), - error = function(e) NULL - ) |> as.data.frame() - }) |> - list_rbind() |> - rlang::set_names(names(means)) - } - # Generate samples for each covariate - means <- covariate$mean - cov_matrix <- pos_def(covariate$cov) # ensure covariance matrix is positive definite, function in PMutilities + + weights <- poppar$wt + means <- bind_rows(poppar$popMean) + cov_matrix <- pos_def(poppar$popCov) # ensure covariance matrix is positive definite, function in PMutilities if (length(cov_matrix) == 1 && cov_matrix == 1) { return(invisible(NULL)) # quietly abort simulation } else if (length(cov_matrix) == 1 && cov_matrix == -1) { cli::cli_abort(c( - "x" = "Covariate parameter covariance matrix cannot be made positive definite.", + "x" = "Population parameter covariance matrix cannot be made positive definite.", "i" = "Please check your covariance matrix." )) } - - samples <- tryCatch( - PM_rtmvnorm( # in PMutilities - n = nrow(pars), - mean = means, - sigma = cov_matrix, - lb = rep(0, length(means)) - ), - error = function(e) NULL - ) |> - tibble::as_tibble(.name_repair = "minimal") |> - rlang::set_names(names(means)) - - # in either case, combine the pars and covariates and proceed - CVsum <- bind_cols(pars, samples, .name_repair = "minimal") |> mutate(icen = "mean") - } else { # we had a PM_final_data as covariate$cov - CVsum <- covariate$cov$summary(icen = "mean") + samples_per_mode <- stats::rmultinom(1, size = 50, prob = weights) + + # Generate samples for each mode + + pars <- map(1:length(weights), \(i) { + tryCatch( + suppressWarnings( + PM_rtmvnorm( # in PMutilities + n = samples_per_mode[i], + mean = means[i, ], + sigma = cov_matrix, + lb = rep(0, ncol(means[i, ])) + ) + ), + error = function(e) NULL + ) |> as.data.frame() + }) |> + list_rbind() |> + rlang::set_names(names(means)) } - - # take out fixed covariates not to be simulated - if (length(covariate$fix) > 0) { - CVsum <- CVsum |> select(.cols = -!!covariate$fix) + # Generate samples for each covariate + means <- covariate$mean + cov_matrix <- pos_def(covariate$cov) # ensure covariance matrix is positive definite, function in PMutilities + if (length(cov_matrix) == 1 && cov_matrix == 1) { + return(invisible(NULL)) # quietly abort simulation + } else if (length(cov_matrix) == 1 && cov_matrix == -1) { + cli::cli_abort(c( + "x" = "Covariate parameter covariance matrix cannot be made positive definite.", + "i" = "Please check your covariance matrix." + )) } - # remove covariates that are missing because they have all the same value - # this also drops id and icen columns - CVsum <- CVsum |> + + samples <- tryCatch( + PM_rtmvnorm( # in PMutilities + n = nrow(pars), + mean = means, + sigma = cov_matrix, + lb = rep(0, length(means)) + ), + error = function(e) NULL + ) |> + tibble::as_tibble(.name_repair = "minimal") |> + rlang::set_names(names(means)) + + # in either case, combine the pars and covariates and proceed + CVsum <- bind_cols(pars, samples, .name_repair = "minimal") |> mutate(icen = "mean") + } else { # we had a PM_final_data as covariate$cov + CVsum <- covariate$cov$summary(icen = "mean") + } + + # take out fixed covariates not to be simulated + if (length(covariate$fix) > 0) { + CVsum <- CVsum |> select(.cols = -!!covariate$fix) + } + # remove covariates that are missing because they have all the same value + # this also drops id and icen columns + CVsum <- CVsum |> select(where(~ dplyr::n_distinct(.) > 1)) |> select(-id) - - # get correlation matrix - corCV <- suppressWarnings(cor(CVsum)) - - nsimcov <- ncol(corCV) - npar - - # augment poppar correlation matrix - bind_bottom_right <- function(A, B, n) { - m <- nrow(A) - if (nrow(B) != m + n || ncol(B) != m + n) { - stop("B must be of size (m + n) x (m + n)") - } - - # Extract blocks from B - B_right <- B[1:m, (m + 1):(m + n), drop = FALSE] # top-right block - B_bottom <- B[(m + 1):(m + n), 1:m, drop = FALSE] # bottom-left block - B_corner <- B[(m + 1):(m + n), (m + 1):(m + n), drop = FALSE] # bottom-right block - - # Assemble full matrix - top <- cbind(A, B_right) - bottom <- cbind(B_bottom, B_corner) - rbind(top, bottom) - } - - corMat <- bind_bottom_right( - as.matrix(poppar$popCor), - as.matrix(corCV), - n = nsimcov - ) - - - # get SD of covariates - covSD <- CVsum |> summarize(across(last_col(offset = nsimcov - 1):last_col(), \(x) sd(x, na.rm = TRUE))) - - # grab their names - covs2sim <- names(covSD) - - # set SDs of named variables, and use population values for others - if (length(covariate$sd) > 0) { - badNames <- which(!names(covariate$sd) %in% names(covSD)) - if (length(badNames) > 0) { - cli::cli_abort(c( - "x" = "The {.arg sd} element of {.arg covariate} must be a list with parameter names.", - "i" = "See {.fn PM_sim} for help." - )) - } - covSD[which(names(covSD) %in% names(covariate$sd))] <- covariate$sd - covSD <- unlist(covSD) - } - # augmented correlation matrix to covariance - covMat <- cor2cov(corMat, unlist(c(poppar$popSD, covSD[1:nsimcov]))) - dimnames(covMat) <- dimnames(corMat) - - # get means of covariates - covMean <- CVsum |> summarize(across(covs2sim, \(x) mean(x, na.rm = TRUE))) - - # set means of named variables, and use population values for others - if (length(covariate$mean) > 0) { - badNames <- which(!names(covariate$mean) %in% names(covMean)) - if (length(badNames) > 0) { - cli::cli_abort(c( - "x" = "The {.arg mean} element of {.arg covariate} must be a list with parameter names.", - "i" = "See {.fn PM_sim} for help." - )) - } - covMean[which(names(covMean) %in% names(covariate$mean))] <- covariate$mean - covMean <- unlist(covMean) + + # get correlation matrix + corCV <- suppressWarnings(cor(CVsum)) + + nsimcov <- ncol(corCV) - npar + + # augment poppar correlation matrix + bind_bottom_right <- function(A, B, n) { + m <- nrow(A) + if (nrow(B) != m + n || ncol(B) != m + n) { + stop("B must be of size (m + n) x (m + n)") } - - - meanVector <- poppar$popMean |> tibble::add_column(!!!as.list(covMean)) - # get the covariate limits - # get min of original population covariates - covMin <- CVsum |> summarize(across(covs2sim, \(x) min(x, na.rm = TRUE))) - # and get max of original population covariates - covMax <- CVsum |> summarize(across(covs2sim, \(x) max(x, na.rm = TRUE))) - - orig_covlim <- tibble::tibble(par = covs2sim, min = unlist(covMin), max = unlist(covMax)) - covLimits <- orig_covlim - if (length(covariate$limits) > 0) { - # covariate limits are supplied as named list - badNames <- which(!names(covariate$limits) %in% names(covMean)) - if (length(badNames) > 0) { - cli::cli_abort(c( - "x" = "The {.arg limit} element of {.arg covariate} must be a list with parameter names.", - "i" = "E.g. {.code limits = list(wt = c(40, 80), age = c(10, 50))}. See {.fn PM_sim} for help." - )) - } - - # figure out which covariates have different limits and change them - - covUpdates <- tibble::enframe(covariate$limits, name = "par", value = "rng") |> + + # Extract blocks from B + B_right <- B[1:m, (m + 1):(m + n), drop = FALSE] # top-right block + B_bottom <- B[(m + 1):(m + n), 1:m, drop = FALSE] # bottom-left block + B_corner <- B[(m + 1):(m + n), (m + 1):(m + n), drop = FALSE] # bottom-right block + + # Assemble full matrix + top <- cbind(A, B_right) + bottom <- cbind(B_bottom, B_corner) + rbind(top, bottom) + } + + corMat <- bind_bottom_right( + as.matrix(poppar$popCor), + as.matrix(corCV), + n = nsimcov + ) + + + # get SD of covariates + covSD <- CVsum |> summarize(across(last_col(offset = nsimcov - 1):last_col(), \(x) sd(x, na.rm = TRUE))) + + # grab their names + covs2sim <- names(covSD) + + # set SDs of named variables, and use population values for others + if (length(covariate$sd) > 0) { + badNames <- which(!names(covariate$sd) %in% names(covSD)) + if (length(badNames) > 0) { + cli::cli_abort(c( + "x" = "The {.arg sd} element of {.arg covariate} must be a list with parameter names.", + "i" = "See {.fn PM_sim} for help." + )) + } + covSD[which(names(covSD) %in% names(covariate$sd))] <- covariate$sd + covSD <- unlist(covSD) + } + # augmented correlation matrix to covariance + covMat <- cor2cov(corMat, unlist(c(poppar$popSD, covSD[1:nsimcov]))) + dimnames(covMat) <- dimnames(corMat) + + # get means of covariates + covMean <- CVsum |> summarize(across(covs2sim, \(x) mean(x, na.rm = TRUE))) + + # set means of named variables, and use population values for others + if (length(covariate$mean) > 0) { + badNames <- which(!names(covariate$mean) %in% names(covMean)) + if (length(badNames) > 0) { + cli::cli_abort(c( + "x" = "The {.arg mean} element of {.arg covariate} must be a list with parameter names.", + "i" = "See {.fn PM_sim} for help." + )) + } + covMean[which(names(covMean) %in% names(covariate$mean))] <- covariate$mean + covMean <- unlist(covMean) + } + + + meanVector <- poppar$popMean |> tibble::add_column(!!!as.list(covMean)) + # get the covariate limits + # get min of original population covariates + covMin <- CVsum |> summarize(across(covs2sim, \(x) min(x, na.rm = TRUE))) + # and get max of original population covariates + covMax <- CVsum |> summarize(across(covs2sim, \(x) max(x, na.rm = TRUE))) + + orig_covlim <- tibble::tibble(par = covs2sim, min = unlist(covMin), max = unlist(covMax)) + covLimits <- orig_covlim + if (length(covariate$limits) > 0) { + # covariate limits are supplied as named list + badNames <- which(!names(covariate$limits) %in% names(covMean)) + if (length(badNames) > 0) { + cli::cli_abort(c( + "x" = "The {.arg limit} element of {.arg covariate} must be a list with parameter names.", + "i" = "E.g. {.code limits = list(wt = c(40, 80), age = c(10, 50))}. See {.fn PM_sim} for help." + )) + } + + # figure out which covariates have different limits and change them + + covUpdates <- tibble::enframe(covariate$limits, name = "par", value = "rng") |> tidyr::unnest_wider(rng, names_sep = "") |> dplyr::rename(min = rng1, max = rng2) - - covLimits <- dplyr::rows_update(covLimits, covUpdates, by = "par") - - # goodNames <- which(names(covMean) %in% names(covariate$limits)) - # if (length(goodNames) > 0) { - # covLimits[goodNames, ] <- t(sapply(1:length(goodNames), function(x) { - # covariate$limits[[x]] - # })) - # } - } - # dimnames(covLimits) <- list(covs2sim, c("lower", "upper")) - # covLimits <- data.frame(covLimits) - - - limits <- rbind(parLimits, covLimits) - - # add simulated covariates to primary block of model object - new_pri <- map(1:nsimcov, \(x) ab(covLimits$min[x], covLimits$max[x])) - names(new_pri) <- covs2sim - arg_list$pri <- c(arg_list$pri, new_pri) - - # remove them from the covariate block of model object - - model_covs <- mod_list$covariates - covs_to_remove <- which(model_covs %in% covs2sim) - arg_list$cov <- arg_list$cov[-covs_to_remove] - if (length(arg_list$cov) == 0) { - arg_list$cov <- NULL # remove covariates if none left + + covLimits <- dplyr::rows_update(covLimits, covUpdates, by = "par") + + # goodNames <- which(names(covMean) %in% names(covariate$limits)) + # if (length(goodNames) > 0) { + # covLimits[goodNames, ] <- t(sapply(1:length(goodNames), function(x) { + # covariate$limits[[x]] + # })) + # } + } + # dimnames(covLimits) <- list(covs2sim, c("lower", "upper")) + # covLimits <- data.frame(covLimits) + + + limits <- rbind(parLimits, covLimits) + + # add simulated covariates to primary block of model object + new_pri <- map(1:nsimcov, \(x) ab(covLimits$min[x], covLimits$max[x])) + names(new_pri) <- covs2sim + arg_list$pri <- c(arg_list$pri, new_pri) + + # remove them from the covariate block of model object + + model_covs <- mod_list$covariates + covs_to_remove <- which(model_covs %in% covs2sim) + arg_list$cov <- arg_list$cov[-covs_to_remove] + if (length(arg_list$cov) == 0) { + arg_list$cov <- NULL # remove covariates if none left + } + + # also remove them from data template + template <- template[, -which(names(template) %in% covs2sim)] + + + # remake both objects + + arg_list <- PM_model$new(arg_list, compile = FALSE)$arg_list # compile later + template <- PM_data$new(template, quiet = TRUE)$standard_data + + + # remake poppar + poppar$popMean <- meanVector + poppar$popCov <- covMat + + + # if split is true, then remake (augment) popPoints by adding mean covariate prior to each point + if (split) { + add_vector_columns <- function(df, v) { + v_df <- as_tibble(as.list(v)) # convert named vector to one-row tibble + df |> bind_cols(v_df[rep(1, nrow(df)), ]) # replicate the row to match df } - - # also remove them from data template - template <- template[, -which(names(template) %in% covs2sim)] - - - # remake both objects - - arg_list <- PM_model$new(arg_list, compile = FALSE)$arg_list # compile later - template <- PM_data$new(template, quiet = TRUE)$standard_data - - - # remake poppar - poppar$popMean <- meanVector - poppar$popCov <- covMat - - - # if split is true, then remake (augment) popPoints by adding mean covariate prior to each point - if (split) { - add_vector_columns <- function(df, v) { - v_df <- as_tibble(as.list(v)) # convert named vector to one-row tibble - df |> bind_cols(v_df[rep(1, nrow(df)), ]) # replicate the row to match df - } - - poppar$popPoints <- poppar$popPoints |> + + poppar$popPoints <- poppar$popPoints |> add_vector_columns(covMean) |> select(-prob, everything(), prob) - } - } else { - simWithCov <- FALSE - limits <- parLimits - } # end if (covariate) block - - - # regardless of covariates or not, 'limits' is the final variable for - # limits on parameters - - - # NOISE ------------------------------------------------------------------- - - if (!all(is.null(noise))) { - # will ignore obs noise for now but add after simulation - if ("out" %in% names(noise)) { - noise1 <- noise |> purrr::list_assign(out = rlang::zap()) - noise2 <- noise["out"] - } else { - noise1 <- noise - noise2 <- NULL - } - - if (length(noise1) > 0) { - template <- private$makeNoise(template, noise1) - } + } + } else { + simWithCov <- FALSE + limits <- parLimits + } # end if (covariate) block + + + # regardless of covariates or not, 'limits' is the final variable for + # limits on parameters + + + # NOISE ------------------------------------------------------------------- + + if (!all(is.null(noise))) { + # will ignore obs noise for now but add after simulation + if ("out" %in% names(noise)) { + noise1 <- noise |> purrr::list_assign(out = rlang::zap()) + noise2 <- noise["out"] } else { - noise1 <- noise2 <- NULL + noise1 <- noise + noise2 <- NULL } - - - # PRED INT ---------------------------------------------------------------- - - template <- if (!all(is.null(predInt))) { - private$makePredInt(template, predInt) + + if (length(noise1) > 0) { + template <- private$makeNoise(template, noise1) } - - # CALL SIMULATOR ---------------------------------------------------------------- - - - template <- PM_data$new(template, quiet = TRUE) - if (simWithCov) { # if simulating with covariates, we need to recompile the model with the new covariates - if (!quiet) cli::cli_inform("Recompiling model to include covariates...") - mod <- PM_model$new(arg_list, quiet = TRUE) # - } else { - mod <- model + } else { + noise1 <- noise2 <- NULL + } + + + # PRED INT ---------------------------------------------------------------- + + template <- if (!all(is.null(predInt))) { + private$makePredInt(template, predInt) + } + + # CALL SIMULATOR ---------------------------------------------------------------- + + + template <- PM_data$new(template, quiet = TRUE) + if (simWithCov) { # if simulating with covariates, we need to recompile the model with the new covariates + if (!quiet) cli::cli_inform("Recompiling model to include covariates...") + mod <- PM_model$new(arg_list, quiet = TRUE) # + } else { + mod <- model + } + + if (length(postToUse) > 0) { + # simulating from posteriors, each posterior matched to a subject + # need to set theta as the posterior mean or median for each subject + ans <- NULL + data_list <- list() + for (i in 1:nsub) { + # get the prior for this subject + thisPrior <- private$getSimPrior( + i = i, + poppar = poppar, + split = split, + postToUse = postToUse[i], + limits = limits, + seed = seed[1], + nsim = nsim, + toInclude = toInclude, msg = msg + ) + # get the template for this subject + sub_template <- PM_data$new(template$standard_data |> filter(id == toInclude[i]), quiet = TRUE) + + # add the simulated values to the list + data_list <- append(data_list, list(private$getSim(thisPrior, sub_template, mod, noise2, msg = msg))) + ans <- thisPrior$ans } - - if (length(postToUse) > 0) { - # simulating from posteriors, each posterior matched to a subject - # need to set theta as the posterior mean or median for each subject - ans <- NULL - data_list <- list() - for (i in 1:nsub) { - # get the prior for this subject - thisPrior <- private$getSimPrior( - i = i, - poppar = poppar, - split = split, - postToUse = postToUse[i], - limits = limits, - seed = seed[1], - nsim = nsim, - toInclude = toInclude, msg = msg - ) - # get the template for this subject - sub_template <- PM_data$new(template$standard_data |> filter(id == toInclude[i]), quiet = TRUE) - - # add the simulated values to the list - data_list <- append(data_list, list(private$getSim(thisPrior, sub_template, mod, noise2, msg = msg))) - ans <- thisPrior$ans - } - - # combine the output - obs <- purrr::list_rbind(map(data_list, \(x) x$obs)) - amt <- purrr::list_rbind(map(data_list, \(x) x$amt)) - - parValues <- purrr::list_rbind(map(data_list, \(x) x$parValues)) |> + + # combine the output + obs <- purrr::list_rbind(map(data_list, \(x) x$obs)) + amt <- purrr::list_rbind(map(data_list, \(x) x$amt)) + + parValues <- purrr::list_rbind(map(data_list, \(x) x$parValues)) |> mutate(id = rep(toInclude, each = !!nsim), nsim = rep(1:!!nsim, nsub)) |> relocate(id, nsim) - total_means <- dplyr::bind_rows(map(data_list, \(x) x$totalMeans)) |> + total_means <- dplyr::bind_rows(map(data_list, \(x) x$totalMeans)) |> mutate(id = toInclude) |> relocate(id) - total_cov <- dplyr::bind_rows(map(data_list, \(x) data.frame(x$totalCov, row.names = NULL))) |> + total_cov <- dplyr::bind_rows(map(data_list, \(x) data.frame(x$totalCov, row.names = NULL))) |> mutate( id = rep(toInclude, each = npar), par = rep(names(poppar$popMean), !!nsub) ) |> relocate(id, par) - total_nsim <- tibble::tibble(id = toInclude, n = purrr::map_dbl(data_list, \(x) x$totalSets)) - - ret <- list( - obs = obs, - amt = amt, - parValues = parValues, - totalSets = total_nsim, - totalMeans = total_means, - totalCov = total_cov, - template = template, - model = mod + total_nsim <- tibble::tibble(id = toInclude, n = purrr::map_dbl(data_list, \(x) x$totalSets)) + + ret <- list( + obs = obs, + amt = amt, + parValues = parValues, + totalSets = total_nsim, + totalMeans = total_means, + totalCov = total_cov, + template = template, + model = mod + ) + + + class(ret) <- c("PM_sim_data", "list") + self$data <- ret + } else { # postToUse is false + + # set theta as nsim rows drawn from prior + if (!useTheta) { + thisPrior <- private$getSimPrior( + i = 1, + poppar = poppar, + split = split, + postToUse = NULL, + limits = limits, + seed = seed[1], + nsim = nsim, + toInclude = toInclude, + msg = msg ) - - - class(ret) <- c("PM_sim_data", "list") - self$data <- ret - } else { # postToUse is false - - # set theta as nsim rows drawn from prior - if (!useTheta) { - thisPrior <- private$getSimPrior( - i = 1, - poppar = poppar, - split = split, - postToUse = NULL, - limits = limits, - seed = seed[1], - nsim = nsim, - toInclude = toInclude, - msg = msg - ) - } else { - thisPrior <- list(thetas = poppar$popPoints) - } - - self$data <- private$getSim(thisPrior, template, mod, noise2, msg = msg) + } else { + thisPrior <- list(thetas = poppar$popPoints) } - - - # MAKE CSV ---------------------------------------------------------------- - - if (!is.null(makecsv)) { - if (nsub * nsim > 100) { - # cli_ask is in PMutilities - ans <- cli_ask("Creating a csv file with {nsub} templates * {nsim} simulations/template = {nsub * nsim} subjects can take a very long time. Do you wish to proceed (y/n)?") - if (tolower(ans) == "n") { - cat("\nAborting simulation...\n") - return() - } - } - - if (file.exists(makecsv)) { - file.remove(makecsv) + + self$data <- private$getSim(thisPrior, template, mod, noise2, msg = msg) + } + + + # MAKE CSV ---------------------------------------------------------------- + + if (!is.null(makecsv)) { + if (nsub * nsim > 100) { + # cli_ask is in PMutilities + ans <- cli_ask("Creating a csv file with {nsub} templates * {nsim} simulations/template = {nsub * nsim} subjects can take a very long time. Do you wish to proceed (y/n)?") + if (tolower(ans) == "n") { + cat("\nAborting simulation...\n") + return() } - - # cycle through template and nsims - - csv <- list() - for (i in unique(template$standard_data$id)) { - this_template <- template$standard_data |> filter(id == i) - for (j in 1:nsim) { - this_sim <- self$obs |> filter(id == i, nsim == j) - this_template$out[this_template$evid == 0] <- this_sim$out - this_template$id <- paste(i, j, sep = "_") - if (simWithCov) { # add back simulated covariate values - - this_template <- this_template |> + } + + if (file.exists(makecsv)) { + file.remove(makecsv) + } + + # cycle through template and nsims + + csv <- list() + for (i in unique(template$standard_data$id)) { + this_template <- template$standard_data |> filter(id == i) + for (j in 1:nsim) { + this_sim <- self$obs |> filter(id == i, nsim == j) + this_template$out[this_template$evid == 0] <- this_sim$out + this_template$id <- paste(i, j, sep = "_") + if (simWithCov) { # add back simulated covariate values + + this_template <- this_template |> mutate(!!!set_names(self$parValues |> select(!!covs2sim) |> slice(j), covs2sim)) - } - csv <- append(csv, list(this_template)) } + csv <- append(csv, list(this_template)) } - - csv <- PM_data$new(list_rbind(csv), quiet = TRUE) - - if (!stringr::str_detect(makecsv, "\\..{3}$")) { - makecsv <- paste0(makecsv, ".csv") - } - csv$save(makecsv) - - cli::cli_inform("The file {.file {makecsv}} was saved in {getwd()}.") } - - - # FINAL RETURN ------------------------------------------------------------ - if (length(msg) > 0) { - cli::cli_alert_info("Simulation messages:") - purrr::walk(msg, \(m) cli::cli_bullets(c("*" = m))) - return(invisible(NULL)) + + csv <- PM_data$new(list_rbind(csv), quiet = TRUE) + + if (!stringr::str_detect(makecsv, "\\..{3}$")) { + makecsv <- paste0(makecsv, ".csv") } - return(self) - }, # end of SIMrun - + csv$save(makecsv) + + cli::cli_inform("The file {.file {makecsv}} was saved in {getwd()}.") + } + + + # FINAL RETURN ------------------------------------------------------------ + if (length(msg) > 0) { + cli::cli_alert_info("Simulation messages:") + purrr::walk(msg, \(m) cli::cli_bullets(c("*" = m))) + return(invisible(NULL)) + } + return(self) + }, # end of SIMrun + + # get prior density + getSimPrior = function(i, poppar, split, postToUse, limits, seed, nsim, toInclude, msg = NULL) { # get prior density - getSimPrior = function(i, poppar, split, postToUse, limits, seed, nsim, toInclude, msg = NULL) { - # get prior density - - - if (inherits(poppar, "NPAG")) { - if (nsim == 0) { # simulate each support point once - thetas <- poppar$popPoints |> mutate(prob = 1 / n()) - total_means <- poppar$popMean - total_cov <- poppar$popCov - total_nsim <- 0 - - - return(list( - thetas = thetas, total_means = total_means, - total_cov = total_cov, - total_nsim = total_nsim - )) - } - - if (nsim < 2 * nrow(poppar$popPoints)) { - split <- FALSE - msg <- c(msg, " {.arg split} set to {.code FALSE} for {.code nsim} less than 2 * number of support points.") - } - - if (split) { - popPoints <- poppar$popPoints - pop_weight <- popPoints$prob - pop_mean <- popPoints |> select(-prob) - ndist <- nrow(popPoints) - pop_cov <- poppar$popCov / ndist - } else { # not split - if (is.null(postToUse)) { # not simulating from posteriors - pop_weight <- 1 - pop_mean <- poppar$popMean - pop_cov <- poppar$popCov - ndist <- 1 - } else { # simulating from posteriors - pop_weight <- 1 - pop_mean <- poppar$postMean[postToUse, ] |> select(-id) - pop_cov <- poppar$postCov[[postToUse]] - ndist <- 1 - } - } - } else { # manually specified prior - pop_weight <- poppar$popWeight - ndist <- length(pop_weight) - if (nsim < 2 * ndist) { - cli::cli_abort(c("x" = "The {.arg nsim} argument must be at least twice the number of modes in the prior.")) - } - pop_mean <- poppar$popMean - pop_cov <- poppar$popCov / ndist + + + if (inherits(poppar, "NPAG")) { + if (nsim == 0) { # simulate each support point once + thetas <- poppar$popPoints |> mutate(prob = 1 / n()) + total_means <- poppar$popMean + total_cov <- poppar$popCov + total_nsim <- 0 + + + return(list( + thetas = thetas, total_means = total_means, + total_cov = total_cov, + total_nsim = total_nsim + )) } - - # override covariance matrix to zero if nsim = 1 - if (nsim == 1) { - pop_cov <- diag(0, nrow(pop_cov)) + + if (nsim < 2 * nrow(poppar$popPoints)) { + split <- FALSE + msg <- c(msg, " {.arg split} set to {.code FALSE} for {.code nsim} less than 2 * number of support points.") } - - pop_cov <- pos_def(pop_cov) # pos_def is in PMutilities - if (length(pop_cov) == 1 && pop_cov == 1) { - return(invisible(NULL)) # quietly abort simulation - } else if (length(pop_cov) == 1 && pop_cov == -1) { - msg <- if (!is.null(postToUse)) { - glue::glue("Unable to fix covariance for template {.code id = {toInclude[i]}}.") - } else { - "Unable to make population covariance positive definite." + + if (split) { + popPoints <- poppar$popPoints + pop_weight <- popPoints$prob + pop_mean <- popPoints |> select(-prob) + ndist <- nrow(popPoints) + pop_cov <- poppar$popCov / ndist + } else { # not split + if (is.null(postToUse)) { # not simulating from posteriors + pop_weight <- 1 + pop_mean <- poppar$popMean + pop_cov <- poppar$popCov + ndist <- 1 + } else { # simulating from posteriors + pop_weight <- 1 + pop_mean <- poppar$postMean[postToUse, ] |> select(-id) + pop_cov <- poppar$postCov[[postToUse]] + ndist <- 1 } - cli::cli_abort(c( - "x" = msg, - "i" = "Please check your data and covariance matrix." - )) } - - # generate samples for theta - set.seed(seed) - thetas <- generate_multimodal_samples(nsim, pop_weight, pop_mean, pop_cov, toInclude[i], limits) - - - return(thetas) - }, # end getSimPrior function' - - # call simulator and process results - getSim = function(thisPrior, template, mod, noise2, msg = NULL) { - thetas <- thisPrior$thetas |> + } else { # manually specified prior + pop_weight <- poppar$popWeight + ndist <- length(pop_weight) + if (nsim < 2 * ndist) { + cli::cli_abort(c("x" = "The {.arg nsim} argument must be at least twice the number of modes in the prior.")) + } + pop_mean <- poppar$popMean + pop_cov <- poppar$popCov / ndist + } + + # override covariance matrix to zero if nsim = 1 + if (nsim == 1) { + pop_cov <- diag(0, nrow(pop_cov)) + } + + pop_cov <- pos_def(pop_cov) # pos_def is in PMutilities + if (length(pop_cov) == 1 && pop_cov == 1) { + return(invisible(NULL)) # quietly abort simulation + } else if (length(pop_cov) == 1 && pop_cov == -1) { + msg <- if (!is.null(postToUse)) { + glue::glue("Unable to fix covariance for template {.code id = {toInclude[i]}}.") + } else { + "Unable to make population covariance positive definite." + } + cli::cli_abort(c( + "x" = msg, + "i" = "Please check your data and covariance matrix." + )) + } + + # generate samples for theta + set.seed(seed) + thetas <- generate_multimodal_samples(nsim, pop_weight, pop_mean, pop_cov, toInclude[i], limits) + + + return(thetas) + }, # end getSimPrior function' + + # call simulator and process results + getSim = function(thisPrior, template, mod, noise2, msg = NULL) { + thetas <- thisPrior$thetas |> select(-prob) |> as.matrix() - mod$compile() # check if compiled and if not, do so - sim_res <- mod$sim(template, thetas) - sim_res$.id <- template$standard_data$id[match(sim_res$id, template$standard_data$id)] - sim_res <- sim_res |> + mod$compile() # check if compiled and if not, do so + sim_res <- mod$sim(template, thetas) + sim_res$.id <- template$standard_data$id[match(sim_res$id, template$standard_data$id)] + sim_res <- sim_res |> rename(comp = state_index, nsim = spp_index, amt = state) |> mutate(nsim = nsim + 1) - - if (identical(mod$model_list$type, "ODE")) { - sim_res <- sim_res |> filter(comp != 0) - } else { - sim_res <- sim_res |> - mutate( - outeq = normalize_engine_index(outeq), - comp = normalize_engine_index(comp) - ) - } - - sim_res <- sim_res |> + + sim_res <- sim_res |> + mutate( + outeq = normalize_engine_index(outeq), + comp = normalize_engine_index(comp) + ) + + sim_res <- sim_res |> arrange(.id, comp, nsim, time, outeq) |> select(-.id) - - obs <- sim_res |> filter(comp == min(comp, na.rm = TRUE)) |> # obs are duplicated in every compartment + + obs <- sim_res |> filter(comp == min(comp, na.rm = TRUE)) |> # obs are duplicated in every compartment select(id, nsim, time, out, outeq) - - amt <- sim_res |> + + amt <- sim_res |> select(id, nsim, time, out = amt, comp) - - # add output noise if specified - if (!all(is.null(noise2))) { - obs <- private$makeNoise(obs, noise2) - } - - ret <- list( + + # add output noise if specified + if (!all(is.null(noise2))) { + obs <- private$makeNoise(obs, noise2) + } + + ret <- list( + obs = obs, + amt = amt, + parValues = thisPrior$thetas |> select(-prob) |> + mutate(nsim = 1:n()) |> relocate(nsim), + totalSets = thisPrior$total_nsim, + totalMeans = thisPrior$total_means, + totalCov = thisPrior$total_cov, + template = template, + model = mod + ) + + + class(ret) <- c("PM_sim_data", "list") # add PM_sim_data class to data + return(ret) + }, # end .sim function + + # Create new simulation objects with results of simulation + populate = function(simout, type) { + if (type == "sim") { + # self$obs <- simout$obs + # self$amt <- simout$amt + # self$parValues <- simout$parValues + # self$totalMeans <- simout$totalMeans + # self$totalCov <- simout$totalCov + self$data <- simout + class(self$data) <- c("PM_sim_data", "list") + } else if (type == "simlist") { + N <- length(simout) # number of templates + nsim <- max(simout[[1]]$obs$id) + obs <- purrr::list_rbind(map(1:N, \(x) pluck(simout, x, 1)), names_to = "id2") |> rename(nsim = id, id = id2) + amt <- purrr::list_rbind(map(1:N, \(x) pluck(simout, x, 2)), names_to = "id2") |> rename(nsim = id, id = id2) + parValues <- purrr::list_rbind(map(1:N, \(x) pluck(simout, x, 3)), names_to = "id2") |> rename(nsim = id, id = id2) + totalSets <- map(1:N, \(x) simout[[x]]$totalSets) + totalMeans <- map(1:N, \(x) pluck(simout, x, 5)) + totalCov <- map(1:N, \(x) data.frame(pluck(simout, x, 6))) + self$data <- list( obs = obs, amt = amt, - parValues = thisPrior$thetas |> select(-prob) |> - mutate(nsim = 1:n()) |> relocate(nsim), - totalSets = thisPrior$total_nsim, - totalMeans = thisPrior$total_means, - totalCov = thisPrior$total_cov, - template = template, - model = mod + parValues = parValues, + totalSets = totalSets, + totalMeans = totalMeans, + totalCov = totalCov ) - - - class(ret) <- c("PM_sim_data", "list") # add PM_sim_data class to data - return(ret) - }, # end .sim function - - # Create new simulation objects with results of simulation - populate = function(simout, type) { - if (type == "sim") { - # self$obs <- simout$obs - # self$amt <- simout$amt - # self$parValues <- simout$parValues - # self$totalMeans <- simout$totalMeans - # self$totalCov <- simout$totalCov - self$data <- simout - class(self$data) <- c("PM_sim_data", "list") - } else if (type == "simlist") { - N <- length(simout) # number of templates - nsim <- max(simout[[1]]$obs$id) - obs <- purrr::list_rbind(map(1:N, \(x) pluck(simout, x, 1)), names_to = "id2") |> rename(nsim = id, id = id2) - amt <- purrr::list_rbind(map(1:N, \(x) pluck(simout, x, 2)), names_to = "id2") |> rename(nsim = id, id = id2) - parValues <- purrr::list_rbind(map(1:N, \(x) pluck(simout, x, 3)), names_to = "id2") |> rename(nsim = id, id = id2) - totalSets <- map(1:N, \(x) simout[[x]]$totalSets) - totalMeans <- map(1:N, \(x) pluck(simout, x, 5)) - totalCov <- map(1:N, \(x) data.frame(pluck(simout, x, 6))) - self$data <- list( - obs = obs, - amt = amt, - parValues = parValues, - totalSets = totalSets, - totalMeans = totalMeans, - totalCov = totalCov - ) - } else if (type == "R6sim") { - # self$obs <- simout$data$obs - # self$amt <- simout$data$amt - # self$parValues <- simout$data$parValues - # self$totalMeans <- simout$data$totalMeans - # self$totalCov <- simout$data$totalCov - if (inherits(simout$data, "PM_simlist")) { - purrr::map(1:length(simout$data), \(x){ - class(simout$data[[x]]) <- c("PM_sim_data", "list") # ensure class is correct - }) - } else { - class(simout$data) <- c("PM_sim_data", "list") # ensure class is correct - } - self$data <- simout$data + } else if (type == "R6sim") { + # self$obs <- simout$data$obs + # self$amt <- simout$data$amt + # self$parValues <- simout$data$parValues + # self$totalMeans <- simout$data$totalMeans + # self$totalCov <- simout$data$totalCov + if (inherits(simout$data, "PM_simlist")) { + purrr::map(1:length(simout$data), \(x){ + class(simout$data[[x]]) <- c("PM_sim_data", "list") # ensure class is correct + }) + } else { + class(simout$data) <- c("PM_sim_data", "list") # ensure class is correct } - return(self) - }, # end populate - - makeNoise = function(template, noise) { - if (!is.list(noise)) { - cli::cli_warn(c( - "!" = "Noise arguments should be a list.", - "i" = "See ?PM_data for details on how to add noise." + self$data <- simout$data + } + return(self) + }, # end populate + + makeNoise = function(template, noise) { + if (!is.list(noise)) { + cli::cli_warn(c( + "!" = "Noise arguments should be a list.", + "i" = "See ?PM_data for details on how to add noise." + )) + return(invisible(template)) + } + + for (i in 1:length(noise)) { + this <- noise[[i]] + this$.col <- names(noise)[i] + if (this$.col %in% c("id", "evid", "addl", "ii", "input", "outeq", "c0", "c1", "c2", "c3")) { + cli::cli_abort(c( + "x" = "{.arg {this$.col}} is a reserved column name.", + "i" = "Please choose another column to add noise." )) - return(invisible(template)) } - - for (i in 1:length(noise)) { - this <- noise[[i]] - this$.col <- names(noise)[i] - if (this$.col %in% c("id", "evid", "addl", "ii", "input", "outeq", "c0", "c1", "c2", "c3")) { - cli::cli_abort(c( - "x" = "{.arg {this$.col}} is a reserved column name.", - "i" = "Please choose another column to add noise." - )) - } - this$coeff <- this[[1]] - if (is.null(this$mode)) { - this$mode <- "add" - } - - # add zeros to coefficients if needed to make up to length 4 - if (length(this$coeff) < 4) { - this$coeff <- c(this$coeff, rep(0, 4 - length(this$coeff))) - } - - # Ensure target is a column in standard_data - if (!this$.col %in% names(template)) { - cli::cli_abort(c( - "x" = "{.arg {this$.col}} is not a column in your data.", - "i" = "Example: {.code noise = list(dose = list(coeff = c(0.1, 0.1)))}" - )) - } - - # make temporary row index to preserve order later - template$index_ <- 1:nrow(template) - - # Dynamically apply the filter - if (!is.null(this$filter)) { - filter_status <- "filtered" - filter_exprs <- rlang::parse_expr(this$filter) - filtered_data <- template |> + this$coeff <- this[[1]] + if (is.null(this$mode)) { + this$mode <- "add" + } + + # add zeros to coefficients if needed to make up to length 4 + if (length(this$coeff) < 4) { + this$coeff <- c(this$coeff, rep(0, 4 - length(this$coeff))) + } + + # Ensure target is a column in standard_data + if (!this$.col %in% names(template)) { + cli::cli_abort(c( + "x" = "{.arg {this$.col}} is not a column in your data.", + "i" = "Example: {.code noise = list(dose = list(coeff = c(0.1, 0.1)))}" + )) + } + + # make temporary row index to preserve order later + template$index_ <- 1:nrow(template) + + # Dynamically apply the filter + if (!is.null(this$filter)) { + filter_status <- "filtered" + filter_exprs <- rlang::parse_expr(this$filter) + filtered_data <- template |> filter(!!filter_exprs) - # Keep the rest - remaining_data <- template |> + # Keep the rest + remaining_data <- template |> filter(magrittr::not(!!filter_exprs)) - } else { - filter_status <- "" - filtered_data <- template - remaining_data <- NULL - } - - # Get the target - target_col <- filtered_data |> select(id, raw = all_of(this$.col)) - - - # Remove temp row index - template <- template |> select(-index_) - - # Add noise - new_target <- data.frame(1:nrow(target_col)) - names(new_target) <- this$.col - if (this$mode == "add") { - target_col <- target_col |> + } else { + filter_status <- "" + filtered_data <- template + remaining_data <- NULL + } + + # Get the target + target_col <- filtered_data |> select(id, raw = all_of(this$.col)) + + + # Remove temp row index + template <- template |> select(-index_) + + # Add noise + new_target <- data.frame(1:nrow(target_col)) + names(new_target) <- this$.col + if (this$mode == "add") { + target_col <- target_col |> rowwise() |> mutate(noisy = raw + suppressWarnings(rnorm(1, mean = 0, sd = this$coeff[[1]] + - this$coeff[[2]] * raw + - this$coeff[[3]] * raw^2 + - this$coeff[[4]] * raw^3 + this$coeff[[2]] * raw + + this$coeff[[3]] * raw^2 + + this$coeff[[4]] * raw^3 ))) |> ungroup() - } else if (this$mode == "exp") { - target_col <- target_col |> + } else if (this$mode == "exp") { + target_col <- target_col |> rowwise() |> mutate(noisy = raw * exp(suppressWarnings(rnorm(1, mean = 0, sd = this$coeff[[1]] + - this$coeff[[2]] * raw + - this$coeff[[3]] * raw^2 + - this$coeff[[4]] * raw^3 + this$coeff[[2]] * raw + + this$coeff[[3]] * raw^2 + + this$coeff[[4]] * raw^3 )))) |> ungroup() - } else { - cli::cli_abort("x" = "Mode must be 'add' or 'exp'.") - } - - # put back the new noisy column - filtered_data[[this$.col]] <- target_col$noisy - - combined <- bind_rows(filtered_data, remaining_data) |> + } else { + cli::cli_abort("x" = "Mode must be 'add' or 'exp'.") + } + + # put back the new noisy column + filtered_data[[this$.col]] <- target_col$noisy + + combined <- bind_rows(filtered_data, remaining_data) |> arrange(index_) |> select(-index_) - # Fix initial times to be 0 in case they were mutated - combined[!duplicated(combined$id), "time"] <- 0 - - template <- combined - } # end for loop for each noise element - - return(template) - }, # end makeNoise function - - makePredInt = function(template, predInt) { - predTimes <- NA - numeqt <- max(template$outeq, na.rm = TRUE) - if (is.list(predInt)) { - # predInt is a list of (start,end,interval) - if (any(sapply(predInt, length) != 3)) { - cli::cli_abort(c("x" = "If a list, each element of predInt must be of the form {.code c(start, end, interval)}.")) + # Fix initial times to be 0 in case they were mutated + combined[!duplicated(combined$id), "time"] <- 0 + + template <- combined + } # end for loop for each noise element + + return(template) + }, # end makeNoise function + + makePredInt = function(template, predInt) { + predTimes <- NA + numeqt <- max(template$outeq, na.rm = TRUE) + if (is.list(predInt)) { + # predInt is a list of (start,end,interval) + if (any(sapply(predInt, length) != 3)) { + cli::cli_abort(c("x" = "If a list, each element of predInt must be of the form {.code c(start, end, interval)}.")) + } + predTimes <- sapply(predInt, function(x) rep(seq(x[1], x[2], x[3]), each = numeqt)) + # catenate columns into single vector + predTimes <- unlist(predTimes) + } else { + # predTimes is not a list + if (length(predInt) == 1) { + # predInt is a single value + if (predInt != 0) { + # it is not zero + predTimes <- rep(seq(0, ceiling(max(template$time, na.rm = T)), predInt)[-1], each = numeqt) } - predTimes <- sapply(predInt, function(x) rep(seq(x[1], x[2], x[3]), each = numeqt)) - # catenate columns into single vector - predTimes <- unlist(predTimes) + # it was 0 so do nothing } else { - # predTimes is not a list - if (length(predInt) == 1) { - # predInt is a single value - if (predInt != 0) { - # it is not zero - predTimes <- rep(seq(0, ceiling(max(template$time, na.rm = T)), predInt)[-1], each = numeqt) - } - # it was 0 so do nothing + # predInt is a single vector of c(start,stop,interval) + if (length(predInt) == 3) { + predTimes <- rep(seq(predInt[1], predInt[2], predInt[3]), each = numeqt) } else { - # predInt is a single vector of c(start,stop,interval) - if (length(predInt) == 3) { - predTimes <- rep(seq(predInt[1], predInt[2], predInt[3]), each = numeqt) - } else { - cli::cli_abort(c( - "x" = "{.var predInt} is misspecified.", - "i" = "See help for {.fn PM_sim}." - )) - } + cli::cli_abort(c( + "x" = "{.var predInt} is misspecified.", + "i" = "See help for {.fn PM_sim}." + )) } } - - # first, add temporary index to ensure id order remains the same - dat2 <- template |> + } + + # first, add temporary index to ensure id order remains the same + dat2 <- template |> mutate(.id = dplyr::dense_rank(id)) - - # second, add predInt if necessary - if (!is.na(predTimes[1])) { - predTimes <- predTimes[predTimes > 0] # remove predictions at time 0 - dat3 <- dat2 |> + + # second, add predInt if necessary + if (!is.na(predTimes[1])) { + predTimes <- predTimes[predTimes > 0] # remove predictions at time 0 + dat3 <- dat2 |> group_by(.id) |> group_map(~ { theseTimes <- predTimes[!predTimes %in% .x$time[.x$evid == 0]] # remove prediction times at times that are specified in template @@ -1843,148 +1847,148 @@ PM_sim <- R6::R6Class( newPred }) |> bind_rows() - new_dat <- bind_rows(dat2, dat3) |> + new_dat <- bind_rows(dat2, dat3) |> arrange(.id, time, outeq) |> select(-.id) - } else { # predInt was not specified - new_dat <- template # the original data without .id - } - new_dat <- new_dat |> mutate(out = ifelse(evid == 0, -1, NA)) # replace all obs with -1 since simulating - return(new_dat) - } # end makePredInt function - ) # end private - ) # end PM_sim - - - #' @keywords internal - #' @name PM_sim - #' @export - PM_sim$run <- function(...) { - lifecycle::deprecate_warn("2.1.0", "PM_sim$run()", details = "Please use PM_sim$new() instead. ?PM_sim for details.") - } - - #' @keywords internal - #' @name PM_sim - #' @export - PM_sim$load <- function(...) { - lifecycle::deprecate_warn("2.1.0", "PM_sim$load()", details = "Please use PM_sim$new() instead. ?PM_sim for details.") - } - - # PLOT -------------------------------------------------------------------- - #' @title Plot Pmetrics Simulation Objects - #' @description - #' `r lifecycle::badge('stable')` - #' - #' Plots *PM_sim* objects with the option to perform a visual and numerical predictive check - #' @details - #' Simulated observations are plotted as quantiles on the y-axis vs. time on the x.axis. If measured - #' observations are included, a visual and numerical predictive check will be performed. - #' The default plot is to omit markers, but if the marker argument is set to `TRUE`, - #' the resulting marker will have the following plotly properties: - #' `list(symbol = "circle-open", color = "black", size = 8)`. By default a grid is - #' omitted. The legend is also omitted by default, but if included, - #' clicking on a quantile item in the legend will hide it in the plot, - #' and double clicking will hide all other quantiles. - #' - #' @method plot PM_sim - #' @param x The name of an *PM_sim* data object generated by [PM_sim] - #' @param include `r template("include")`. - #' @param exclude `r template("exclude")`. - #' @param mult `r template("mult")` - #' @param ci Width of confidence interval bands around simulated quantiles, - #' from 0 to 1. If 0, or *nsim*<100, will not plot. - #' Default is 0.95, i.e. 95th percentile with tails of 2.5 percent above and below excluded. - #' @param binSize Width of binning interval for simulated concentrations, in time units, e.g. hours. - #' A `binSize` of 0.5 will pull all simulated concentrations +/- 0.5 hours into - #' the same time. This is useful - #' for plotting PMsim objects made during [make_valid]. The default is 0, i.e. no binning. - #' If an `obs` object is provided, it will be binned similarly. - #' @param outeq `r template("outeq")` - #' @param line Controls the appearance of lines. It can be specified in several ways. - #' * Default is `TRUE` which results in simulated profiles summarized - #' as quantiles, with default values of 0.05, 0.25, 0.5, 0.75, and 0.95. The default - #' format will be applied, which is solid black lines of width 1. - #' Numerical predictive checking will be calculated if observations are also included - #' (see *obs* below). - #' * `FALSE` results in no lines plotted and the plot will be blank. - #' * `NA` Quantile summaries will be suppressed, but lines joining simulated outputs - #' will be plotted in default format as above. In other words, all profiles will be plotted, - #' not just the quantiles. Numerical predictive checking will be suppressed. - #' * List of quantiles and formats to plot with the following elements: - #' - `probs` Vector of quantiles to include. If missing, will be set to - #' defaults above, i.e., `c(0.05, 0.5, and 0.95)` - #' Example: `line = list(probs = c(0.25, 0.5, 0.75))`. - #' - `color` Vector of color names whose order corresponds to `probs`. - #' If shorter than `probs`, will be recycled. Default is "dodgerblue", but if - #' median is present (`prob = 0.5`), that line will be "red". - #' Examples: `line = list(color = "red")` or `line = list(color = c("red", "blue"))`. - #' - `fill` Fill color between quantile lines. Can be specified in several ways: - #' * `FALSE` (the default) will not fill between lines. - #' * `TRUE` will fill between lines with a default color of "dodgerblue", opacity 0.2. - #' * A list with the following elements: - #' - `color` Fill color name. Default is "dodgerblue", e.g., `fill = list(color = "red")`. - #' - `opacity` Fill opacity. Default is 0.2 e.g., `fill = list(opacity = 0.3)`. - #' - `probs` Vector of paired quantiles to fill between. Default is the minimum and maximum - #' quantile specified in `probs`, or `fill = list(probs = c(0.05, 0.95))` if not specified. - #' Including `probs` in fill which are not in `probs` above will result in an error. - #' - `width` Vector of widths in pixels, as for `color`. Default is 1. - #' Example: `line = list(width = 2)`. - #' - `dash` Vector of dash types, as for color. Default is "solid". - #' See `plotly::schema()`, traces > scatter > attributes > line > dash > values. - #' Example: `line = list(dash = "dashdot")`. - #' @param marker `r template("marker")` Formatting will only be applied to observations - #' if included via the `obs` argument. - #' @param obs The name of a [PM_result] data object or the PM_op field in the - #' PM_result object, all generated by [PM_load]. For example, if - #' `run1 <- PM_load(1)` and `sim1` is a PM_sim object, then - #' `sim1$plot(obs = run1)` or `sim1$plot(obs = run1$op)`. - #' If specified, - #' the observations will be overlaid upon the simulation plot - #' enabling a visual predictive check. In this case, - #' a list object will be returned with two items: $npc containing the quantiles - #' and probability that the observations - #' are below each quantile (binomial test); and $simsum, the times of each - #' observation and the - #' value of the simulated quantile with upper and lower confidence intervals at that time. - #' Additionally, the number of observations beyond the 5th and 95th percentiles will be reported - #' and the binomial test P-value if this number is different than the expected 10% value. - #' @param quiet If `TRUE`, suppresses the message about simulation report generation, defaults to `FALSE`. - #' @param legend `r template("legend")` Default is `FALSE` - #' @param log `r template("log")` Default is `TRUE`. - #' @param grid `r template("grid")` Default is `FALSE` - #' @param xlab `r template("xlab")` Default is "Time". - #' @param ylab `r template("ylab")` Default is "Output". - #' @param title `r template("title")` Default is to have no title. - #' @param xlim `r template("xlim")` - #' @param ylim `r template("ylim")` - #' @param print If `TRUE`, will print the plotly object and return it. If `FALSE`, will only return the plotly object. - #' @param ... `r template("dotsPlotly")` - #' @return Plots the simulation object. If `obs` is included, a list will be returned with - #' the folowing items: - #' * *npc* A dataframe with three columns: quantile, prop_less, pval. - #' ** *quantile* are those specified by the `probs` argument to the plot call - #' ** *prop_less* are the proportion of simulated - #' observations at all times less than the quantile - #' ** *pval* is the P-value of the difference in the - #' prop.less and quantile by the beta-binomial test. - #' * *simsum* A dataframe with the quantile concentration at each simulated time, - #' with lower and upper confidence intervals - #' * *obs* A data frame similar to a the `$data` field of a [PM_op] object - #' with the addition of the quantile for each observation - #' @author Michael Neely - #' @seealso [PM_sim], [plot_ly], [schema] - #' @importFrom tidyr unnest_longer - #' @importFrom dplyr summarize - #' @export - #' @examples - #' \dontrun{ - #' simEx$plot() - #' simEx$plot(log = FALSE, line = list(color = "orange")) - #' } - - #' @family PMplots - - plot.PM_sim <- function( + } else { # predInt was not specified + new_dat <- template # the original data without .id + } + new_dat <- new_dat |> mutate(out = ifelse(evid == 0, -1, NA)) # replace all obs with -1 since simulating + return(new_dat) + } # end makePredInt function + ) # end private +) # end PM_sim + + +#' @keywords internal +#' @name PM_sim +#' @export +PM_sim$run <- function(...) { + lifecycle::deprecate_warn("2.1.0", "PM_sim$run()", details = "Please use PM_sim$new() instead. ?PM_sim for details.") +} + +#' @keywords internal +#' @name PM_sim +#' @export +PM_sim$load <- function(...) { + lifecycle::deprecate_warn("2.1.0", "PM_sim$load()", details = "Please use PM_sim$new() instead. ?PM_sim for details.") +} + +# PLOT -------------------------------------------------------------------- +#' @title Plot Pmetrics Simulation Objects +#' @description +#' `r lifecycle::badge('stable')` +#' +#' Plots *PM_sim* objects with the option to perform a visual and numerical predictive check +#' @details +#' Simulated observations are plotted as quantiles on the y-axis vs. time on the x.axis. If measured +#' observations are included, a visual and numerical predictive check will be performed. +#' The default plot is to omit markers, but if the marker argument is set to `TRUE`, +#' the resulting marker will have the following plotly properties: +#' `list(symbol = "circle-open", color = "black", size = 8)`. By default a grid is +#' omitted. The legend is also omitted by default, but if included, +#' clicking on a quantile item in the legend will hide it in the plot, +#' and double clicking will hide all other quantiles. +#' +#' @method plot PM_sim +#' @param x The name of an *PM_sim* data object generated by [PM_sim] +#' @param include `r template("include")`. +#' @param exclude `r template("exclude")`. +#' @param mult `r template("mult")` +#' @param ci Width of confidence interval bands around simulated quantiles, +#' from 0 to 1. If 0, or *nsim*<100, will not plot. +#' Default is 0.95, i.e. 95th percentile with tails of 2.5 percent above and below excluded. +#' @param binSize Width of binning interval for simulated concentrations, in time units, e.g. hours. +#' A `binSize` of 0.5 will pull all simulated concentrations +/- 0.5 hours into +#' the same time. This is useful +#' for plotting PMsim objects made during [make_valid]. The default is 0, i.e. no binning. +#' If an `obs` object is provided, it will be binned similarly. +#' @param outeq `r template("outeq")` +#' @param line Controls the appearance of lines. It can be specified in several ways. +#' * Default is `TRUE` which results in simulated profiles summarized +#' as quantiles, with default values of 0.05, 0.25, 0.5, 0.75, and 0.95. The default +#' format will be applied, which is solid black lines of width 1. +#' Numerical predictive checking will be calculated if observations are also included +#' (see *obs* below). +#' * `FALSE` results in no lines plotted and the plot will be blank. +#' * `NA` Quantile summaries will be suppressed, but lines joining simulated outputs +#' will be plotted in default format as above. In other words, all profiles will be plotted, +#' not just the quantiles. Numerical predictive checking will be suppressed. +#' * List of quantiles and formats to plot with the following elements: +#' - `probs` Vector of quantiles to include. If missing, will be set to +#' defaults above, i.e., `c(0.05, 0.5, and 0.95)` +#' Example: `line = list(probs = c(0.25, 0.5, 0.75))`. +#' - `color` Vector of color names whose order corresponds to `probs`. +#' If shorter than `probs`, will be recycled. Default is "dodgerblue", but if +#' median is present (`prob = 0.5`), that line will be "red". +#' Examples: `line = list(color = "red")` or `line = list(color = c("red", "blue"))`. +#' - `fill` Fill color between quantile lines. Can be specified in several ways: +#' * `FALSE` (the default) will not fill between lines. +#' * `TRUE` will fill between lines with a default color of "dodgerblue", opacity 0.2. +#' * A list with the following elements: +#' - `color` Fill color name. Default is "dodgerblue", e.g., `fill = list(color = "red")`. +#' - `opacity` Fill opacity. Default is 0.2 e.g., `fill = list(opacity = 0.3)`. +#' - `probs` Vector of paired quantiles to fill between. Default is the minimum and maximum +#' quantile specified in `probs`, or `fill = list(probs = c(0.05, 0.95))` if not specified. +#' Including `probs` in fill which are not in `probs` above will result in an error. +#' - `width` Vector of widths in pixels, as for `color`. Default is 1. +#' Example: `line = list(width = 2)`. +#' - `dash` Vector of dash types, as for color. Default is "solid". +#' See `plotly::schema()`, traces > scatter > attributes > line > dash > values. +#' Example: `line = list(dash = "dashdot")`. +#' @param marker `r template("marker")` Formatting will only be applied to observations +#' if included via the `obs` argument. +#' @param obs The name of a [PM_result] data object or the PM_op field in the +#' PM_result object, all generated by [PM_load]. For example, if +#' `run1 <- PM_load(1)` and `sim1` is a PM_sim object, then +#' `sim1$plot(obs = run1)` or `sim1$plot(obs = run1$op)`. +#' If specified, +#' the observations will be overlaid upon the simulation plot +#' enabling a visual predictive check. In this case, +#' a list object will be returned with two items: $npc containing the quantiles +#' and probability that the observations +#' are below each quantile (binomial test); and $simsum, the times of each +#' observation and the +#' value of the simulated quantile with upper and lower confidence intervals at that time. +#' Additionally, the number of observations beyond the 5th and 95th percentiles will be reported +#' and the binomial test P-value if this number is different than the expected 10% value. +#' @param quiet If `TRUE`, suppresses the message about simulation report generation, defaults to `FALSE`. +#' @param legend `r template("legend")` Default is `FALSE` +#' @param log `r template("log")` Default is `TRUE`. +#' @param grid `r template("grid")` Default is `FALSE` +#' @param xlab `r template("xlab")` Default is "Time". +#' @param ylab `r template("ylab")` Default is "Output". +#' @param title `r template("title")` Default is to have no title. +#' @param xlim `r template("xlim")` +#' @param ylim `r template("ylim")` +#' @param print If `TRUE`, will print the plotly object and return it. If `FALSE`, will only return the plotly object. +#' @param ... `r template("dotsPlotly")` +#' @return Plots the simulation object. If `obs` is included, a list will be returned with +#' the folowing items: +#' * *npc* A dataframe with three columns: quantile, prop_less, pval. +#' ** *quantile* are those specified by the `probs` argument to the plot call +#' ** *prop_less* are the proportion of simulated +#' observations at all times less than the quantile +#' ** *pval* is the P-value of the difference in the +#' prop.less and quantile by the beta-binomial test. +#' * *simsum* A dataframe with the quantile concentration at each simulated time, +#' with lower and upper confidence intervals +#' * *obs* A data frame similar to a the `$data` field of a [PM_op] object +#' with the addition of the quantile for each observation +#' @author Michael Neely +#' @seealso [PM_sim], [plot_ly], [schema] +#' @importFrom tidyr unnest_longer +#' @importFrom dplyr summarize +#' @export +#' @examples +#' \dontrun{ +#' simEx$plot() +#' simEx$plot(log = FALSE, line = list(color = "orange")) +#' } + +#' @family PMplots + +plot.PM_sim <- function( x, include, exclude, @@ -2002,314 +2006,313 @@ PM_sim <- R6::R6Class( xlab, ylab, title, xlim, ylim, - print = TRUE, ... - ) { - if (all(is.na(line))) { - line <- list(probs = NA) - } # standardize - if (is.logical(line)) { - if (line) { - lineList <- list( - probs = c(0.05, 0.5, 0.95), - color = c("dodgerblue", "red", "dodgerblue"), - width = rep(1, 3), - dash = rep("solid", 3) - ) # line = T - } else { # line = F - lineList <- amendLine(FALSE) - lineList$probs <- NULL # line was FALSE, so probs = NULL -> no join lines or quantiles - lineList$width <- 0 - } - } else { # line was not T/F - if (!is.list(line) & length(line) > 1) { - cli::cli_abort(c( - "x" = "{.arg Line} is misspecified.", - "i" = "If you want to specify quantiles, use {.code line = list(probs = c(...), ...)}." - )) - } - if (!is.null(purrr::pluck(line, "probs"))) { - lineList <- list(probs = line$probs) - } else { - lineList <- list(probs = c(0.05, 0.5, 0.95)) + print = TRUE, ...) { + if (all(is.na(line))) { + line <- list(probs = NA) + } # standardize + if (is.logical(line)) { + if (line) { + lineList <- list( + probs = c(0.05, 0.5, 0.95), + color = c("dodgerblue", "red", "dodgerblue"), + width = rep(1, 3), + dash = rep("solid", 3) + ) # line = T + } else { # line = F + lineList <- amendLine(FALSE) + lineList$probs <- NULL # line was FALSE, so probs = NULL -> no join lines or quantiles + lineList$width <- 0 + } + } else { # line was not T/F + if (!is.list(line) & length(line) > 1) { + cli::cli_abort(c( + "x" = "{.arg Line} is misspecified.", + "i" = "If you want to specify quantiles, use {.code line = list(probs = c(...), ...)}." + )) + } + if (!is.null(purrr::pluck(line, "probs"))) { + lineList <- list(probs = line$probs) + } else { + lineList <- list(probs = c(0.05, 0.5, 0.95)) + } + nprobs <- length(lineList$probs) + if (!is.null(purrr::pluck(line, "color"))) { + lineList$color <- rep(line$color, nprobs)[1:nprobs] + } else { + lineList$color <- rep("dodgerblue", nprobs) + # color median red if present + medProb <- which(lineList$probs == 0.5) + if (length(medProb) == 0) { + lineList$color[medProb] <- "red" } - nprobs <- length(lineList$probs) - if (!is.null(purrr::pluck(line, "color"))) { - lineList$color <- rep(line$color, nprobs)[1:nprobs] - } else { - lineList$color <- rep("dodgerblue", nprobs) - # color median red if present - medProb <- which(lineList$probs == 0.5) - if (length(medProb) == 0) { - lineList$color[medProb] <- "red" + } + if (!is.null(purrr::pluck(line, "fill"))) { + if (is.logical(line$fill && line$fill)) { + lineList$fill <- list(color = "dodgerblue", opacity = 0.2, probs = range(lineList$probs)) + } else if (is.logical(line$fill && !line$fill)) { + lineList$fill <- NULL + } else if (is.list(line$fill)) { + if (is.null(purrr::pluck(line$fill, "color"))) { + line$fill$color <- "dodgerblue" } - } - if (!is.null(purrr::pluck(line, "fill"))) { - if (is.logical(line$fill && line$fill)) { - lineList$fill <- list(color = "dodgerblue", opacity = 0.2, probs = range(lineList$probs)) - } else if (is.logical(line$fill && !line$fill)) { - lineList$fill <- NULL - } else if (is.list(line$fill)) { - if (is.null(purrr::pluck(line$fill, "color"))) { - line$fill$color <- "dodgerblue" - } - if (is.null(purrr::pluck(line$fill, "opacity"))) { - line$fill$opacity <- 0.2 - } - if (is.null(purrr::pluck(line$fill, "probs"))) { - line$fill$probs <- range(lineList$probs) - } - lineList$fill <- line$fill - } else { - cli::cli_abort(c( - "x" = "{.arg fill} is misspecified.", - "i" = "If you want to specify fill between quantiles, use {.code line = list(fill = list(...), ...)}." - )) + if (is.null(purrr::pluck(line$fill, "opacity"))) { + line$fill$opacity <- 0.2 } - } - - if (!is.null(lineList$fill$probs) && !all(lineList$fill$probs %in% lineList$probs)) { + if (is.null(purrr::pluck(line$fill, "probs"))) { + line$fill$probs <- range(lineList$probs) + } + lineList$fill <- line$fill + } else { cli::cli_abort(c( - "x" = "{.arg fill} quantiles must be a subset of {.arg probs}.", - "i" = "Please check your {.code line} argument." + "x" = "{.arg fill} is misspecified.", + "i" = "If you want to specify fill between quantiles, use {.code line = list(fill = list(...), ...)}." )) } - - - if (!is.null(purrr::pluck(line, "width"))) { - lineList$width <- rep(line$width, nprobs)[1:nprobs] - } else { - lineList$width <- rep(1, nprobs) - } - if (!is.null(purrr::pluck(line, "dash"))) { - lineList$dash <- rep(line$dash, nprobs)[1:nprobs] - } else { - lineList$dash <- rep("solid", nprobs) - } } - - - probValues <- lineList$probs - probFormats <- lineList[-1] |> - purrr::transpose() |> - purrr::simplify_all() - join <- amendLine(probFormats[[1]]) - - # parse marker - if (!missing(obs)) { - if (!is.list(marker) && !marker) marker <- T + + if (!is.null(lineList$fill$probs) && !all(lineList$fill$probs %in% lineList$probs)) { + cli::cli_abort(c( + "x" = "{.arg fill} quantiles must be a subset of {.arg probs}.", + "i" = "Please check your {.code line} argument." + )) } - marker <- amendMarker(marker, default = list(color = "black", symbol = "circle-open", size = 8)) - - # process dots - layout <- amendDots(list(...)) - - # axis labels - xlab <- if (missing(xlab)) { - "Time" + + + if (!is.null(purrr::pluck(line, "width"))) { + lineList$width <- rep(line$width, nprobs)[1:nprobs] } else { - xlab + lineList$width <- rep(1, nprobs) } - ylab <- if (missing(ylab)) { - "Output" + if (!is.null(purrr::pluck(line, "dash"))) { + lineList$dash <- rep(line$dash, nprobs)[1:nprobs] } else { - ylab + lineList$dash <- rep("solid", nprobs) } - - layout$xaxis$title <- amendTitle(xlab) - if (is.character(ylab)) { - layout$yaxis$title <- amendTitle(ylab, layout$xaxis$title$font) + } + + + probValues <- lineList$probs + probFormats <- lineList[-1] |> + purrr::transpose() |> + purrr::simplify_all() + join <- amendLine(probFormats[[1]]) + + # parse marker + if (!missing(obs)) { + if (!is.list(marker) && !marker) marker <- T + } + marker <- amendMarker(marker, default = list(color = "black", symbol = "circle-open", size = 8)) + + # process dots + layout <- amendDots(list(...)) + + # axis labels + xlab <- if (missing(xlab)) { + "Time" + } else { + xlab + } + ylab <- if (missing(ylab)) { + "Output" + } else { + ylab + } + + layout$xaxis$title <- amendTitle(xlab) + if (is.character(ylab)) { + layout$yaxis$title <- amendTitle(ylab, layout$xaxis$title$font) + } else { + layout$yaxis$title <- amendTitle(ylab) + } + + # grid + layout$xaxis <- setGrid(layout$xaxis, grid) + layout$yaxis <- setGrid(layout$yaxis, grid) + + # axis ranges + if (!missing(xlim)) { + layout$xaxis <- modifyList(layout$xaxis, list(range = xlim)) + } + if (!missing(ylim)) { + layout$yaxis <- modifyList(layout$yaxis, list(range = ylim)) + } + + # log y axis + if (log) { + layout$yaxis <- modifyList(layout$yaxis, list(type = "log")) + } + + # title + if (missing(title)) { + title <- "" + } + layout$title <- amendTitle(title, default = list(size = 20)) + + + # legend + legendList <- amendLegend(legend, default = list(title = list(text = " Quantiles "))) + layout <- modifyList(layout, list(showlegend = legendList[[1]], legend = legendList[-1])) + + + # numerical check function + NPsimInterp <- function(time, out, sim_sum, probs) { + if (min(sim_sum$time) <= time) { + lower_time <- max(sim_sum$time[sim_sum$time <= time], na.rm = TRUE) } else { - layout$yaxis$title <- amendTitle(ylab) - } - - # grid - layout$xaxis <- setGrid(layout$xaxis, grid) - layout$yaxis <- setGrid(layout$yaxis, grid) - - # axis ranges - if (!missing(xlim)) { - layout$xaxis <- modifyList(layout$xaxis, list(range = xlim)) + return(NA) } - if (!missing(ylim)) { - layout$yaxis <- modifyList(layout$yaxis, list(range = ylim)) - } - - # log y axis - if (log) { - layout$yaxis <- modifyList(layout$yaxis, list(type = "log")) - } - - # title - if (missing(title)) { - title <- "" + if (max(sim_sum$time >= time)) { + upper_time <- min(sim_sum$time[sim_sum$time >= time], na.rm = TRUE) + } else { + return(NA) } - layout$title <- amendTitle(title, default = list(size = 20)) - - - # legend - legendList <- amendLegend(legend, default = list(title = list(text = " Quantiles "))) - layout <- modifyList(layout, list(showlegend = legendList[[1]], legend = legendList[-1])) - - - # numerical check function - NPsimInterp <- function(time, out, sim_sum, probs) { - if (min(sim_sum$time) <= time) { - lower_time <- max(sim_sum$time[sim_sum$time <= time], na.rm = TRUE) + sim_quantile <- 0 + for (i in probs) { + if (lower_time != upper_time) { + lower_sim <- sim_sum$value[sim_sum$time == lower_time & sim_sum$quantile == i] + upper_sim <- sim_sum$value[sim_sum$time == upper_time & sim_sum$quantile == i] + slope <- (upper_sim - lower_sim) / (upper_time - lower_time) + calc_sim <- lower_sim + slope * (time - lower_time) } else { - return(NA) + calc_sim <- sim_sum$value[sim_sum$time == lower_time & sim_sum$quantile == i] } - if (max(sim_sum$time >= time)) { - upper_time <- min(sim_sum$time[sim_sum$time >= time], na.rm = TRUE) - } else { - return(NA) + if (out >= calc_sim) { + sim_quantile <- i } - sim_quantile <- 0 - for (i in probs) { - if (lower_time != upper_time) { - lower_sim <- sim_sum$value[sim_sum$time == lower_time & sim_sum$quantile == i] - upper_sim <- sim_sum$value[sim_sum$time == upper_time & sim_sum$quantile == i] - slope <- (upper_sim - lower_sim) / (upper_time - lower_time) - calc_sim <- lower_sim + slope * (time - lower_time) - } else { - calc_sim <- sim_sum$value[sim_sum$time == lower_time & sim_sum$quantile == i] - } - if (out >= calc_sim) { - sim_quantile <- i - } - } - return(sim_quantile) } - - # process x - if (inherits(x, "PM_sim")) { - simout <- x$data - } else if (inherits(x, "PM_sim_data")) { - simout <- x - } else { - cli::cli_abort(c( - "x" = "{.cls PM_sim} or {.cls PM_sim_list} object required.", - "i" = "See help for {.fn PM_sim}." - )) + return(sim_quantile) + } + + # process x + if (inherits(x, "PM_sim")) { + simout <- x$data + } else if (inherits(x, "PM_sim_data")) { + simout <- x + } else { + cli::cli_abort(c( + "x" = "{.cls PM_sim} or {.cls PM_sim_list} object required.", + "i" = "See help for {.fn PM_sim}." + )) + } + + # include/exclude template ids + if (missing(include)) include <- unique(simout$obs$id) + if (missing(exclude)) exclude <- NA + simout$obs <- simout$obs |> includeExclude(include, exclude) + + if (!missing(obs)) { + if (!inherits(obs, c("PM_result", "PM_op"))) { + cli::cli_abort(c("x" = "Supply a {.cls PM_result} or {.cls PM_op} object for the {.code obs} argument.")) } - - # include/exclude template ids - if (missing(include)) include <- unique(simout$obs$id) - if (missing(exclude)) exclude <- NA - simout$obs <- simout$obs |> includeExclude(include, exclude) - - if (!missing(obs)) { - if (!inherits(obs, c("PM_result", "PM_op"))) { - cli::cli_abort(c("x" = "Supply a {.cls PM_result} or {.cls PM_op} object for the {.code obs} argument.")) - } - if (inherits(obs, "PM_result")) { - obs <- obs$op$data - } - if (inherits(obs, "PM_op")) { - obs <- obs$data - } - obs <- obs |> + if (inherits(obs, "PM_result")) { + obs <- obs$op$data + } + if (inherits(obs, "PM_op")) { + obs <- obs$data + } + obs <- obs |> filter( outeq == !!outeq, icen == "median", pred.type == "post" ) |> select(id, time, obs) # just need obs; median and post are arbitrary - } else { - obs <- data.frame(time = NA, obs = NA) - } - - # change <=0 to NA if log plot - if (log) { - if (all(is.na(obs$obs))) { - if (any(simout$obs <= 0, na.rm = TRUE)) { - if (!quiet) { - cat("Values <= 0 omitted from log plot.\n") - } - simout$obs[simout$obs <= 0] <- NA + } else { + obs <- data.frame(time = NA, obs = NA) + } + + # change <=0 to NA if log plot + if (log) { + if (all(is.na(obs$obs))) { + if (any(simout$obs <= 0, na.rm = TRUE)) { + if (!quiet) { + cat("Values <= 0 omitted from log plot.\n") } - } else { - if (any(obs$obs <= 0, na.rm = TRUE) | any(simout$obs <= 0, na.rm = TRUE)) { - if (!quiet) { - cat("Values <= 0 omitted from log plot.\n") - } - obs$obs[obs$obs <= 0] <- NA - simout$obs[simout$obs <= 0] <- NA + simout$obs[simout$obs <= 0] <- NA + } + } else { + if (any(obs$obs <= 0, na.rm = TRUE) | any(simout$obs <= 0, na.rm = TRUE)) { + if (!quiet) { + cat("Values <= 0 omitted from log plot.\n") } + obs$obs[obs$obs <= 0] <- NA + simout$obs[simout$obs <= 0] <- NA } } - - # multiply - simout$obs$out <- simout$obs$out * mult - obs$obs <- obs$obs * mult - - # simplify - sim_out <- simout$obs[!is.na(simout$obs$out), ] - - # bin times if requested - if (binSize > 0) { - binned_sim_times <- seq(floor(min(sim_out$time, na.rm = TRUE)), ceiling(max(sim_out$time, na.rm = TRUE)), binSize) - sim_out$time <- binned_sim_times[.bincode(sim_out$time, binned_sim_times, include.lowest = TRUE)] - sim_out <- sim_out |> + } + + # multiply + simout$obs$out <- simout$obs$out * mult + obs$obs <- obs$obs * mult + + # simplify + sim_out <- simout$obs[!is.na(simout$obs$out), ] + + # bin times if requested + if (binSize > 0) { + binned_sim_times <- seq(floor(min(sim_out$time, na.rm = TRUE)), ceiling(max(sim_out$time, na.rm = TRUE)), binSize) + sim_out$time <- binned_sim_times[.bincode(sim_out$time, binned_sim_times, include.lowest = TRUE)] + sim_out <- sim_out |> group_by(id, time, outeq) |> summarize(out = mean(out), .groups = "drop") - if (!all(is.na(obs$obs))) { - binned_obs_times <- seq(floor(min(obs$time, na.rm = TRUE)), ceiling(max(obs$time, na.rm = TRUE)), binSize) - obs$time <- binned_obs_times[.bincode(obs$time, binned_obs_times, include.lowest = TRUE)] - obs <- obs |> + if (!all(is.na(obs$obs))) { + binned_obs_times <- seq(floor(min(obs$time, na.rm = TRUE)), ceiling(max(obs$time, na.rm = TRUE)), binSize) + obs$time <- binned_obs_times[.bincode(obs$time, binned_obs_times, include.lowest = TRUE)] + obs <- obs |> group_by(id, time) |> summarize(obs = mean(obs), .groups = "drop") - } } - - nout <- max(sim_out$outeq) - nsim <- nrow(simout$parValues) - - - sim <- sim_out |> filter(outeq == !!outeq) - times <- sort(unique(sim$time)) - nobs <- length(times) - - if (!all(is.na(probValues)) & nsim >= 10) { - # make DF of time, quantile and value - sim_quant_df <- sim |> + } + + nout <- max(sim_out$outeq) + nsim <- nrow(simout$parValues) + + + sim <- sim_out |> filter(outeq == !!outeq) + times <- sort(unique(sim$time)) + nobs <- length(times) + + if (!all(is.na(probValues)) & nsim >= 10) { + # make DF of time, quantile and value + sim_quant_df <- sim |> dplyr::group_by(time) |> group_map(~ quantile(.x$out, probs = probValues, na.rm = TRUE)) |> dplyr::tibble() |> tidyr::unnest_longer(1, indices_to = "quantile", values_to = "value") |> dplyr::mutate( time = rep(times, each = length(probValues)), - quantile = readr::parse_number(quantile) / 100 + quantile = rep(probValues, times = length(times)) ) |> dplyr::select(time, quantile, value) - - lower_confint <- function(n) { - l.ci <- ceiling(n * probValues - qnorm(1 - (1 - ci) / 2) * sqrt(n * probValues * (1 - probValues))) - l.ci[l.ci == 0] <- NA - return(l.ci) - } - - upper_confint <- function(n) { - u.ci <- ceiling(n * probValues + qnorm(1 - (1 - ci) / 2) * sqrt(n * probValues * (1 - probValues))) - return(u.ci) - } - - lconfint <- tapply(sim$out, sim$time, function(x) sort(x)[lower_confint(length(x))]) - uconfint <- tapply(sim$out, sim$time, function(x) sort(x)[upper_confint(length(x))]) - - sim_quant_df$lowerCI <- as.numeric(unlist(lconfint)) - sim_quant_df$upperCI <- as.numeric(unlist(uconfint)) - - # plot main data - p <- sim_quant_df |> + + lower_confint <- function(n) { + l.ci <- ceiling(n * probValues - qnorm(1 - (1 - ci) / 2) * sqrt(n * probValues * (1 - probValues))) + l.ci[l.ci == 0] <- NA + return(l.ci) + } + + upper_confint <- function(n) { + u.ci <- ceiling(n * probValues + qnorm(1 - (1 - ci) / 2) * sqrt(n * probValues * (1 - probValues))) + return(u.ci) + } + + lconfint <- tapply(sim$out, sim$time, function(x) sort(x)[lower_confint(length(x))]) + uconfint <- tapply(sim$out, sim$time, function(x) sort(x)[upper_confint(length(x))]) + + sim_quant_df$lowerCI <- as.numeric(unlist(lconfint)) + sim_quant_df$upperCI <- as.numeric(unlist(uconfint)) + + # plot main data + p <- sim_quant_df |> group_by(quantile) |> plotly::plot_ly(x = ~time, y = ~value) - - # add confidence intervals - if (nsim < 100) { - if (!quiet) { - cat("\nNote: Confidence intervals for simulation quantiles omitted when nsim < 100\n") - } - } else { - p <- p |> + + # add confidence intervals + if (nsim < 100) { + if (!quiet) { + cat("\nNote: Confidence intervals for simulation quantiles omitted when nsim < 100\n") + } + } else { + p <- p |> plotly::add_ribbons( ymin = ~lowerCI, ymax = ~upperCI, name = "CI", @@ -2318,34 +2321,34 @@ PM_sim <- R6::R6Class( hoverinfo = "none", showlegend = FALSE ) + } + + # add quantile lines, allowing for the independent formats + for (i in 1:length(probValues)) { + thisQ <- sim_quant_df |> filter(quantile == probValues[i]) + p <- p |> plotly::add_lines( + x = ~time, y = ~value, data = thisQ, line = probFormats[[i]], + hovertemplate = "Time: %{x}
Out: %{y}
Quantile: %{text}", + text = ~quantile, + name = ~quantile + ) + } + + # add fill if specified + if (!is.null(lineList$fill)) { + if (ci > 0) { + cli::cli_inform(c( + "i" = "Consider {.code ci = 0} for cleaner plot.", + " " = "See {.help PM_sim$plot()} for details." + )) } - - # add quantile lines, allowing for the independent formats - for (i in 1:length(probValues)) { - thisQ <- sim_quant_df |> filter(quantile == probValues[i]) - p <- p |> plotly::add_lines( - x = ~time, y = ~value, data = thisQ, line = probFormats[[i]], - hovertemplate = "Time: %{x}
Out: %{y}
Quantile: %{text}", - text = ~quantile, - name = ~quantile - ) - } - - # add fill if specified - if (!is.null(lineList$fill)) { - if (ci > 0) { - cli::cli_inform(c( - "i" = "Consider {.code ci = 0} for cleaner plot.", - " " = "See {.help PM_sim$plot()} for details." - )) - } - fill_area <- sim_quant_df |> + fill_area <- sim_quant_df |> filter(quantile %in% lineList$fill$probs) |> select(time, quantile, value) |> tidyr::pivot_wider(names_from = quantile, values_from = value) |> rename(lower = 2, upper = 3) - - p <- p |> + + p <- p |> plotly::add_ribbons( data = fill_area, x = ~time, ymin = ~lower, ymax = ~upper, name = "CI", @@ -2354,18 +2357,18 @@ PM_sim <- R6::R6Class( fillcolor = lineList$fill$color, opacity = lineList$fill$opacity ) - } - - - retValue <- list() - - # add observations if supplied, and calculate NPC - if (!all(is.na(obs))) { - p <- p |> add_markers(x = ~time, y = ~obs, data = obs, marker = marker) - obs$sim_quant <- NA - - for (i in 1:nrow(obs)) { - obs$sim_quant[i] <- ifelse(is.na(obs$obs[i]), NA, + } + + + retValue <- list() + + # add observations if supplied, and calculate NPC + if (!all(is.na(obs))) { + p <- p |> add_markers(x = ~time, y = ~obs, data = obs, marker = marker) + obs$sim_quant <- NA + + for (i in 1:nrow(obs)) { + obs$sim_quant[i] <- ifelse(is.na(obs$obs[i]), NA, NPsimInterp(obs$time[i], obs$obs[i], sim_quant_df, probs = probValues) ) } @@ -2377,7 +2380,7 @@ PM_sim <- R6::R6Class( ) for (i in 1:nrow(npc)) { success <- sum(as.numeric(obs$sim_quant < probValues[i]), na.rm = TRUE) - + pval <- tryCatch( binom.test(success, not.miss, probValues[i], alternative = "two" @@ -2387,56 +2390,56 @@ PM_sim <- R6::R6Class( npc$prop_less[i] <- round(success / not.miss, 3) npc$pval[i] <- pval } - + # calculate proportion between 0.05 and 0.95 between <- rep(NA, nrow(obs)) for (i in 1:nrow(obs)) { between[i] <- ifelse(is.na(obs$obs[i]), NA, - NPsimInterp(obs$time[i], obs$obs[i], sim_quant_df, probs = c(0.05, 0.95)) - ) + NPsimInterp(obs$time[i], obs$obs[i], sim_quant_df, probs = c(0.05, 0.95)) + ) + } + success90 <- sum(as.numeric(between >= 0.05 & between < 0.95), na.rm = TRUE) + attr(npc, "05-95") <- success90 / not.miss + attr(npc, "P-90") <- binom.test(success90, not.miss, 0.9, "two")$p.value + + if (not.miss < nrow(obs)) { + cat(paste("\n", nrow(obs) - not.miss, " observed values were obtained beyond the \nsimulated time range of ", min(sim_quant_df$time), " to ", max(sim_quant_df$time), " and were excluded.", sep = "")) + } + + + retValue <- modifyList(retValue, list(npc = npc, simsum = sim_quant_df, obs = obs)) + class(retValue) <- c("PMnpc", "list") } - success90 <- sum(as.numeric(between >= 0.05 & between < 0.95), na.rm = TRUE) - attr(npc, "05-95") <- success90 / not.miss - attr(npc, "P-90") <- binom.test(success90, not.miss, 0.9, "two")$p.value - - if (not.miss < nrow(obs)) { - cat(paste("\n", nrow(obs) - not.miss, " observed values were obtained beyond the \nsimulated time range of ", min(sim_quant_df$time), " to ", max(sim_quant_df$time), " and were excluded.", sep = "")) + } else { # probs was set to NA or nsim < 10 + + + # plot all simulated profiles + p <- sim |> + mutate(id2 = paste(id, nsim, sep = "_")) |> + group_by(id2) |> + plotly::plot_ly(x = ~time, y = ~out) |> + plotly::add_lines(line = join) + # plot observations if available + if (!all(is.na(obs))) { + p <- p |> add_markers(x = ~time, y = ~obs, data = obs, marker = marker) } - - - retValue <- modifyList(retValue, list(npc = npc, simsum = sim_quant_df, obs = obs)) - class(retValue) <- c("PMnpc", "list") + retValue <- list() } -} else { # probs was set to NA or nsim < 10 - - - # plot all simulated profiles - p <- sim |> - mutate(id2 = paste(id, nsim, sep = "_")) |> - group_by(id2) |> - plotly::plot_ly(x = ~time, y = ~out) |> - plotly::add_lines(line = join) - # plot observations if available - if (!all(is.na(obs))) { - p <- p |> add_markers(x = ~time, y = ~obs, data = obs, marker = marker) - } - retValue <- list() -} -# common to all plots -p <- p |> plotly::layout( - xaxis = layout$xaxis, - yaxis = layout$yaxis, - showlegend = layout$showlegend, - legend = layout$legend, - title = layout$title -) + # common to all plots + p <- p |> plotly::layout( + xaxis = layout$xaxis, + yaxis = layout$yaxis, + showlegend = layout$showlegend, + legend = layout$legend, + title = layout$title + ) -if (print) print(p) -retValue <- modifyList(retValue, list(p = p)) -return(invisible(retValue)) + if (print) print(p) + retValue <- modifyList(retValue, list(p = p)) + return(invisible(retValue)) } # SUMMARY ----------------------------------------------------------------- @@ -2483,10 +2486,9 @@ return(invisible(retValue)) #' @seealso [PM_sim] #' @export summary.PM_sim <- function( - object, include, exclude, field = "obs", group = NULL, - statistics = c("mean", "sd", "median", "min", "max"), - digits = getPMoptions("digits"), ... -) { + object, include, exclude, field = "obs", group = NULL, + statistics = c("mean", "sd", "median", "min", "max"), + digits = getPMoptions("digits"), ...) { # get the right data if (inherits(object, "PM_sim")) { dat <- object$data[[field]] @@ -2501,8 +2503,8 @@ summary.PM_sim <- function( } else { cli::cli_abort(c("x" = "Object does not appear to be a simulation.")) } - - + + # loop through all the statistics summ <- purrr::map( statistics, @@ -2520,8 +2522,8 @@ summary.PM_sim <- function( } } ) - - + + names(summ) <- as.character(statistics) if (!"id" %in% group) { summ <- purrr::map(summ, \(x) { @@ -2530,13 +2532,13 @@ summary.PM_sim <- function( }) } summ <- summ |> - list_rbind(names_to = "stat") |> - mutate(across(where(is.numeric), \(x) round(x, digits))) + list_rbind(names_to = "stat") |> + mutate(across(where(is.numeric), \(x) round(x, digits))) if (length(group) > 0) { attr(summ, "group") <- group } class(summ) <- c("summary.PM_sim", "data.frame") - + return(summ) } @@ -2584,7 +2586,7 @@ generate_multimodal_samples <- function(num_samples, weights, means, cov_matrix, if (length(weights) != length(means)) { cli::cli_abort("Weights and means must have the same length.") } - + # handle malformed covariance if (any(is.na(cov_matrix))) { cov_matrix[is.na(cov_matrix)] <- 0 @@ -2593,29 +2595,29 @@ generate_multimodal_samples <- function(num_samples, weights, means, cov_matrix, "i" = "Values were set to 0, resulting in identical simulations." )) } - + # Determine number of samples from each mode - - + + samples_per_mode <- stats::rmultinom(1, size = num_samples, prob = weights) - + # function used later to check if any parameters are outside their limits outside_check <- function(x) { any(x - limits$min < 0) | # any parameter < lower limit - any(x - limits$max > 0) # any parameter > upper limit + any(x - limits$max > 0) # any parameter > upper limit } - + # Generate samples bounded by limits for each mode all_samples <- map(1:length(weights), function(j) { samples <- tryCatch(suppressWarnings(MASS::mvrnorm(n = samples_per_mode[j, ], mu = as.matrix(means[[j]], nrow = 1), Sigma = cov_matrix)), error = function(e) NULL) - + # replace any outside their limits if (!is.null(samples)) { if (!is.matrix(samples)) { samples <- as.data.frame(as.list(samples)) names(samples) <- names(means[[j]]) } - + discarded <- NULL if (!all(is.null(limits))) { for (k in 1:nrow(samples)) { @@ -2635,31 +2637,31 @@ generate_multimodal_samples <- function(num_samples, weights, means, cov_matrix, } } # end loop to fix thetas out of range } - - + + list(keep = samples, discard = discarded) # the final set of samples for this mode } }) - - + + retained <- all_samples |> - map(\(x) x$keep) |> - (\(x) do.call(rbind, x))() |> - tibble::as_tibble(.name_repair = "minimal") |> - mutate(prob = 1 / dplyr::n()) - + map(\(x) x$keep) |> + (\(x) do.call(rbind, x))() |> + tibble::as_tibble(.name_repair = "minimal") |> + mutate(prob = 1 / dplyr::n()) + discarded <- all_samples |> - map(\(x) x$discard) |> - (\(x) do.call(rbind, x))() |> - tibble::as_tibble(.name_repair = "minimal") |> - mutate(prob = 1 / dplyr::n()) - + map(\(x) x$discard) |> + (\(x) do.call(rbind, x))() |> + tibble::as_tibble(.name_repair = "minimal") |> + mutate(prob = 1 / dplyr::n()) + total_means <- apply(rbind(retained, discarded), 2, mean)[1:ncol(cov_matrix)] total_cov <- bind_rows(retained, discarded) |> - select(-prob) |> - cov() + select(-prob) |> + cov() total_nsim <- sum(nrow(retained), nrow(discarded)) # sum will ignore NULL values - + return(list( thetas = retained, total_means = total_means, total_cov = total_cov, diff --git a/R/PM_valid.R b/R/PM_valid.R index 31c4eb749..fb3bb6639 100755 --- a/R/PM_valid.R +++ b/R/PM_valid.R @@ -209,7 +209,7 @@ PM_valid <- R6::R6Class( tidyr::unnest_longer(col = 1, indices_to = "quantile", values_to = "value") |> dplyr::mutate( time = rep(times, each = length(q_probs)), - quantile = readr::parse_number(quantile) / 100 + quantile = rep(q_probs, times = length(times)) ) |> dplyr::select(time, quantile, value) } diff --git a/R/PMbuild.R b/R/PMbuild.R index 1d957b86e..a72d9883c 100755 --- a/R/PMbuild.R +++ b/R/PMbuild.R @@ -15,43 +15,13 @@ PM_build <- function() { - if (is_rustup_installed()) { - cli::cli_text("Rust was detected in your system, Fetching dependencies and building base project.") - template_path <- resolve_template_path() - dummy_compile(template_path = template_path) - } else { - cli::cli_text("Rust was not detected in your system, this can be caused by multiple reasons:") - ul <- cli::cli_ul() - cli::cli_li("You have not installed rustup in your system, Follow the installation instructions at https://www.rust-lang.org/tools/install") - cli::cli_li("You might have rustup installed in your system but your $PATH has not been updated (Windows), try closing and re-opening your R session, and/or Rstudio/Positron.") - cli::cli_li("If you are using linux/MacOS and this error persists after installing rust, try using this command in your terminal: {.code sudo ln -s ~/.cargo/bin/* /usr/local/sbin}") - cli::cli_end(ul) - cli::cli_text("If this error persists, please refer to our discussions website: https://github.com/LAPKB/Pmetrics/discussions.") - } + cli::cli_inform(c( + "i" = "{.fn PM_build()} is no longer required with the DSL runtime.", + " " = "Models are validated directly from {.code PM_model$dsl()} during fit and simulation." + )) + invisible(NULL) } -resolve_template_path <- function() { - package_path <- system.file(package = "Pmetrics") - in_check <- nzchar(Sys.getenv("_R_CHECK_PACKAGE_NAME_")) - in_development <- Sys.getenv("env") == "Development" - in_cargo_workspace <- nzchar(package_path) && file.exists(file.path(package_path, "Cargo.toml")) - - if (in_development || in_check || in_cargo_workspace) { - return(file.path(temporary_path(), "template")) - } - - package_path -} - -is_rustup_installed <- function() { - flag <- is_cargo_installed() - # Sometimes R does not find rustup even if it is installed, - # Fix: create a symlink to any of the folders watched by system("echo $PATH") - # sudo ln -s ~/.cargo/bin/* /usr/local/sbin - # for rustup and cargo - # We cannot do it automatically because it requires elevated permissions - return(flag) -} is_rustup_installed <- function() { flag <- is_cargo_installed() # Sometimes R does not find rustup even if it is installed, diff --git a/R/extendr-wrappers.R b/R/extendr-wrappers.R index 846834596..9909d26b8 100755 --- a/R/extendr-wrappers.R +++ b/R/extendr-wrappers.R @@ -10,43 +10,36 @@ #' @useDynLib Pmetrics, .registration = TRUE NULL -#' Simulates the first subject in the data set using the model at the given path. +#' Simulates the first subject in the data set using the model DSL source. #' @param data_path Path to the data file. -#' @param model_path Path to the compiled model file. +#' @param model_source DSL source code for the model. #' @param spp One support point as a numeric vector with probabiltity. #' @param kind Kind of model, which can either be "ODE" or "Analytical". +#' @param solver Optional ODE solver name. #' @return Simulation results. #' @keywords internal #' @export -simulate_one <- function(data_path, model_path, spp, kind) .Call(wrap__simulate_one, data_path, model_path, spp, kind) +simulate_one <- function(data_path, model_source, spp, kind, solver = NULL) .Call(wrap__simulate_one, data_path, model_source, spp, kind, solver) -#' Simulates all subjects in the data set using the model at the given path. +#' Simulates all subjects in the data set using the model DSL source. #' @param data_path Path to the data file. -#' @param model_path Path to the compiled model file. +#' @param model_source DSL source code for the model. #' @param theta Data frame of support points. #' @param kind Kind of model, which can either be "ODE" or "Analytical". +#' @param solver Optional ODE solver name. #' @return Simulation results. #' @keywords internal #' @export -simulate_all <- function(data_path, model_path, theta, kind) .Call(wrap__simulate_all, data_path, model_path, theta, kind) +simulate_all <- function(data_path, model_source, theta, kind, solver = NULL) .Call(wrap__simulate_all, data_path, model_source, theta, kind, solver) -#' Compiles the text representation of a model into a binary file. -#' @param model_path Path to the model file. -#' @param output_path Path to save the compiled model. -#' @param params List of model parameters. -#' @param template_path Path to the template directory. +#' Validates DSL model source against the runtime compiler. +#' @param model_source DSL source code for the model. #' @param kind Kind of model, which can either be "ODE" or "Analytical". -#' @return Result of the compilation process. +#' @param solver Optional ODE solver name. +#' @return Result of the validation process. #' @keywords internal #' @export -compile_model <- function(model_path, output_path, params, template_path, kind) .Call(wrap__compile_model, model_path, output_path, params, template_path, kind) - -#' Dummy function to cache compilation artifacts. -#' @param template_path Path to the template directory. -#' @return Path to the build directory. -#' @keywords internal -#' @export -dummy_compile <- function(template_path) .Call(wrap__dummy_compile, template_path) +validate_model_source <- function(model_source, kind, solver = NULL) .Call(wrap__validate_model_source, model_source, kind, solver) #' Checks if Cargo is installed on the system. #' @return TRUE if Cargo is installed, FALSE otherwise. @@ -54,30 +47,25 @@ dummy_compile <- function(template_path) .Call(wrap__dummy_compile, template_pat #' @export is_cargo_installed <- function() .Call(wrap__is_cargo_installed) -#' Fits the model at the given path to the data at the given path using the provided parameters. -#' @param model_path Path to the compiled model file. +#' Fits the model DSL source to the data using the provided parameters. +#' @param model_source DSL source code for the model. #' @param data Path to the data file. #' @param params List of fitting parameters. #' @param output_path Path to save the fitting results. #' @param kind Kind of model, which can either be "ODE" or "Analytical". +#' @param solver Optional ODE solver name. #' @return Result of the fitting process. #' @keywords internal #' @export -fit <- function(model_path, data, params, output_path, kind) .Call(wrap__fit, model_path, data, params, output_path, kind) +fit <- function(model_source, data, params, output_path, kind, solver = NULL) .Call(wrap__fit, model_source, data, params, output_path, kind, solver) -#' Retrieves the model parameters from the compiled model at the given path. -#' @param model_path Path to the compiled model file. +#' Retrieves the model parameters from compiled DSL metadata. +#' @param model_source DSL source code for the model. #' @param kind Kind of model, which can either be "ODE" or "Analytical". #' @return List of model parameters. #' @keywords internal #' @export -model_parameters <- function(model_path, kind) .Call(wrap__model_parameters, model_path, kind) - -#' Retrieves the temporary path used for building models. -#' @return Temporary build path. -#' @keywords internal -#' @export -temporary_path <- function() .Call(wrap__temporary_path) +model_parameters <- function(model_source, kind) .Call(wrap__model_parameters, model_source, kind) #' Initialize the tracing subscriber with the custom R formatter #' @keywords internal diff --git a/R/model_dsl.R b/R/model_dsl.R new file mode 100644 index 000000000..c4e20e2bc --- /dev/null +++ b/R/model_dsl.R @@ -0,0 +1,1275 @@ +statement_exprs <- function(fun_or_expr) { + if (is.null(fun_or_expr)) { + return(list()) + } + + expr <- if (is.function(fun_or_expr)) body(fun_or_expr) else fun_or_expr + if (is.call(expr) && identical(expr[[1]], as.name("{"))) { + return(as.list(expr[-1])) + } + + list(expr) +} + + +reserved_name_conflicts <- function(blocks) { + reserved <- c("t", "x", "dx", "p", "b", "bolus", "r", "rateiv", "cov", "y") + + block_lines <- function(block) { + if (is.null(block)) { + return(character(0)) + } + + if (is.function(block)) { + return(tolower(func_to_char(block))) + } + + if (is.environment(block)) { + return(character(0)) + } + + if (is.list(block)) { + block_names <- names(block) + block_names <- if (is.null(block_names)) character(0) else tolower(block_names) + return(c(block_names, unlist(lapply(block, block_lines), use.names = FALSE))) + } + + if (!is.atomic(block) && !is.character(block)) { + return(character(0)) + } + + tolower(as.character(block)) + } + + conflicts <- unique(unlist(lapply(blocks, function(block) { + lines <- block_lines(block) + if (length(lines) == 0) { + return(character(0)) + } + + unlist(lapply(reserved, function(name) { + has_match <- name %in% lines || any(stringr::str_detect(lines, paste0("^", name, "\\s+[=<]"))) + if (has_match) { + return(name) + } + NA_character_ + }), use.names = FALSE) + }), use.names = FALSE)) + + conflicts[!is.na(conflicts)] +} + + +expr_text <- function(expr) { + paste(deparse(expr, width.cutoff = 500L), collapse = "\n") +} + + +index_vector_size <- function(max_index) { + if (max_index <= 0L) { + return(1L) + } + + max_index + 1L +} + + +get_max_index <- function(fn_or_expr, targets) { + targets <- tolower(targets) + + walk_expr <- function(expr) { + if (is.call(expr) && identical(expr[[1]], as.name("["))) { + target_name <- tolower(as.character(expr[[2]])) + if (target_name %in% targets) { + idx <- expr[[3]] + if (is.numeric(idx) && length(idx) == 1) { + return(as.integer(idx)) + } + } + } + + if (is.call(expr)) { + return(max(vapply(as.list(expr), walk_expr, integer(1)), 0L)) + } + + 0L + } + expr <- if (is.function(fn_or_expr)) body(fn_or_expr) else fn_or_expr + walk_expr(expr) +} + + +collect_index_refs <- function(fn_or_expr, targets) { + targets <- tolower(targets) + found <- integer(0) + + walk_expr <- function(expr) { + if (!is.call(expr)) { + return(invisible(NULL)) + } + + if (identical(expr[[1]], as.name("["))) { + target_name <- tolower(as.character(expr[[2]])) + idx <- expr[[3]] + if (target_name %in% targets && is.numeric(idx) && length(idx) == 1) { + found <<- c(found, as.integer(idx)) + } + } + + lapply(as.list(expr), walk_expr) + invisible(NULL) + } + + expr <- if (is.function(fn_or_expr)) body(fn_or_expr) else fn_or_expr + walk_expr(expr) + sort(unique(found)) +} + + +get_assignment_indices <- function(fn_or_expr, targets) { + targets <- tolower(targets) + found <- integer(0) + + walk_expr <- function(expr) { + if (!is.call(expr)) { + return(invisible(NULL)) + } + + if (identical(expr[[1]], as.name("<-")) || identical(expr[[1]], as.name("="))) { + lhs <- expr[[2]] + if (is.call(lhs) && identical(lhs[[1]], as.name("["))) { + target_name <- tolower(as.character(lhs[[2]])) + idx <- lhs[[3]] + if (target_name %in% targets && is.numeric(idx) && length(idx) == 1) { + found <<- c(found, as.integer(idx)) + } + } + } + + lapply(as.list(expr), walk_expr) + invisible(NULL) + } + + expr <- if (is.function(fn_or_expr)) body(fn_or_expr) else fn_or_expr + walk_expr(expr) + sort(unique(found)) +} + + +get_assignments <- function(fn_or_expr, targets) { + length(get_assignment_indices(fn_or_expr, targets)) +} + + +get_max_assignment_index <- function(fn_or_expr, targets) { + found <- get_assignment_indices(fn_or_expr, targets) + if (length(found) == 0) { + return(0L) + } + + max(found) +} + + +has_nonliteral_index <- function(fn_or_expr, targets) { + targets <- tolower(targets) + + walk_expr <- function(expr) { + if (is.call(expr) && identical(expr[[1]], as.name("["))) { + target_name <- tolower(as.character(expr[[2]])) + if (target_name %in% targets) { + idx <- expr[[3]] + return(!(is.numeric(idx) && length(idx) == 1)) + } + } + + if (is.call(expr)) { + return(any(vapply(as.list(expr), walk_expr, logical(1)))) + } + + FALSE + } + + expr <- if (is.function(fn_or_expr)) body(fn_or_expr) else fn_or_expr + walk_expr(expr) +} + + +collect_assigned_names_from_exprs <- function(exprs) { + found <- character(0) + + walk_expr <- function(expr) { + if (!is.call(expr)) { + return(invisible(NULL)) + } + + op <- as.character(expr[[1]]) + if (op %in% c("<-", "=")) { + lhs <- expr[[2]] + if (is.symbol(lhs)) { + found <<- c(found, tolower(as.character(lhs))) + } + } + + lapply(as.list(expr), walk_expr) + invisible(NULL) + } + + lapply(exprs, walk_expr) + unique(found) +} + + +collect_assigned_names <- function(fun_or_expr) { + collect_assigned_names_from_exprs(statement_exprs(fun_or_expr)) +} + + +normalize_ode_solver <- function(solver) { + if (!is.character(solver) || length(solver) != 1) { + cli::cli_abort(c( + "x" = "{.arg solver} must be a single character value.", + "i" = "Supported values are 'BDF', 'TRBDF2', 'ESDIRK34', and 'TSIT45'." + )) + } + + switch(tolower(solver), + bdf = "BDF", + trbdf2 = "TRBDF2", + esdirk34 = "ESDIRK34", + tsit45 = "TSIT45", + cli::cli_abort(c( + "x" = "Unsupported {.arg solver} value: {.val {solver}}.", + "i" = "Supported values are 'BDF', 'TRBDF2', 'ESDIRK34', and 'TSIT45'." + )) + ) +} + + +dsl_covariate_declarations <- function(covariates) { + if (is.null(covariates) || length(covariates) == 0) { + return(character(0)) + } + + purrr::imap_chr(covariates, \(interpolation, name) { + annotation <- if (identical(interpolation, 0)) "@locf" else "@linear" + paste0(tolower(name), annotation) + }) +} + + +dsl_analytical_spec <- function(template_name) { + switch(tolower(template_name), + one_comp_iv = list( + structure = "one_compartment", + states = c("central"), + routes = list( + list(kind = "bolus", input = 1L, destination = "central"), + list(kind = "infusion", input = 1L, destination = "central") + ) + ), + one_comp_iv_cl = list( + structure = "one_compartment_cl", + states = c("central"), + routes = list( + list(kind = "bolus", input = 1L, destination = "central"), + list(kind = "infusion", input = 1L, destination = "central") + ) + ), + one_comp_bolus = list( + structure = "one_compartment_with_absorption", + states = c("gut", "central"), + routes = list( + list(kind = "bolus", input = 1L, destination = "gut"), + list(kind = "infusion", input = 1L, destination = "central") + ) + ), + one_comp_bolus_cl = list( + structure = "one_compartment_cl_with_absorption", + states = c("gut", "central"), + routes = list( + list(kind = "bolus", input = 1L, destination = "gut"), + list(kind = "infusion", input = 1L, destination = "central") + ) + ), + two_comp_iv = list( + structure = "two_compartments", + params = c("ke", "kcp", "kpc", "v"), + symbols = c(k12 = "kcp", k21 = "kpc"), + states = c("central", "peripheral"), + routes = list( + list(kind = "bolus", input = 1L, destination = "central"), + list(kind = "infusion", input = 1L, destination = "central") + ) + ), + two_comp_iv_cl = list( + structure = "two_compartments_cl", + params = c("cl", "q", "vc", "vp"), + symbols = c(v1 = "vc", v2 = "vp"), + states = c("central", "peripheral"), + routes = list( + list(kind = "bolus", input = 1L, destination = "central"), + list(kind = "infusion", input = 1L, destination = "central") + ) + ), + two_comp_bolus = list( + structure = "two_compartments_with_absorption", + params = c("ke", "ka", "kcp", "kpc", "v"), + symbols = c(k23 = "kcp", k32 = "kpc"), + states = c("gut", "central", "peripheral"), + routes = list( + list(kind = "bolus", input = 1L, destination = "gut"), + list(kind = "infusion", input = 1L, destination = "central") + ) + ), + two_comp_bolus_cl = list( + structure = "two_compartments_cl_with_absorption", + params = c("ka", "cl", "q", "vc", "vp"), + symbols = c(v2 = "vc", v3 = "vp"), + states = c("gut", "central", "peripheral"), + routes = list( + list(kind = "bolus", input = 1L, destination = "gut"), + list(kind = "infusion", input = 1L, destination = "central") + ) + ), + three_comp_iv = list( + structure = "three_compartments", + params = c("k10", "k12", "k13", "k21", "k31", "v"), + symbols = c(ke = "k10"), + states = c("central", "peripheral_1", "peripheral_2"), + routes = list( + list(kind = "bolus", input = 1L, destination = "central"), + list(kind = "infusion", input = 1L, destination = "central") + ) + ), + three_comp_iv_cl = list( + structure = "three_compartments_cl", + params = c("cl", "q2", "q3", "vc", "v2", "v3"), + symbols = c(v1 = "vc"), + states = c("central", "peripheral_1", "peripheral_2"), + routes = list( + list(kind = "bolus", input = 1L, destination = "central"), + list(kind = "infusion", input = 1L, destination = "central") + ) + ), + three_comp_bolus = list( + structure = "three_compartments_with_absorption", + params = c("ka", "k10", "k12", "k13", "k21", "k31", "v"), + symbols = c(ke = "k10", k23 = "k12", k24 = "k13", k32 = "k21", k42 = "k31"), + states = c("gut", "central", "peripheral_1", "peripheral_2"), + routes = list( + list(kind = "bolus", input = 1L, destination = "gut"), + list(kind = "infusion", input = 1L, destination = "central") + ) + ), + three_comp_bolus_cl = list( + structure = "three_compartments_cl_with_absorption", + params = c("ka", "cl", "q2", "q3", "vc", "v2", "v3"), + symbols = c(q3 = "q2", q4 = "q3", v2 = "vc", v3 = "v2", v4 = "v3"), + states = c("gut", "central", "peripheral_1", "peripheral_2"), + routes = list( + list(kind = "bolus", input = 1L, destination = "gut"), + list(kind = "infusion", input = 1L, destination = "central") + ) + ), + cli::cli_abort(c( + "x" = "Unsupported analytical model template: {.val {template_name}}." + )) + ) +} + + +is_template_statement <- function(expr, template_name) { + expr_name <- tolower(expr_text(expr)) + if (expr_name == tolower(template_name)) { + return(TRUE) + } + + alt_names <- alt_mod_lib_names() |> + dplyr::filter(primary == template_name) |> + dplyr::pull(alt) |> + tolower() + + expr_name %in% alt_names +} + + +collect_route_usage <- function(fun_or_expr, state_names) { + usage <- list() + exprs <- if (is.list(fun_or_expr) && !is.function(fun_or_expr)) { + fun_or_expr + } else { + statement_exprs(fun_or_expr) + } + + register_usage <- function(kind, input_index, destination) { + key <- paste(kind, input_index, sep = ":") + existing <- usage[[key]] + if (!is.null(existing) && !identical(existing$destination, destination)) { + cli::cli_abort(c( + "x" = "Input {.val {input_index}} is routed to multiple destinations in the same model.", + "i" = "Each bolus or infusion input must target exactly one state in the DSL lowering." + )) + } + + usage[[key]] <<- list(kind = kind, input = as.integer(input_index), destination = destination) + } + + for (expr in exprs) { + if (!is.call(expr) || !as.character(expr[[1]]) %in% c("<-", "=")) { + next + } + + lhs <- expr[[2]] + if (!is.call(lhs) || !identical(lhs[[1]], as.name("["))) { + next + } + + target <- tolower(as.character(lhs[[2]])) + idx <- lhs[[3]] + if (target != "dx" || !is.numeric(idx) || length(idx) != 1) { + next + } + + destination <- unname(state_names[[as.character(as.integer(idx))]]) + rhs <- expr[[3]] + + for (input_index in collect_index_refs(rhs, c("b", "bolus"))) { + register_usage("bolus", input_index, destination) + } + + for (input_index in collect_index_refs(rhs, c("r", "rateiv"))) { + register_usage("infusion", input_index, destination) + } + } + + usage <- unname(usage) + if (length(usage) == 0) { + return(list()) + } + + usage[order( + vapply(usage, \(item) item$input, integer(1)), + match(vapply(usage, \(item) item$kind, character(1)), c("bolus", "infusion")) + )] +} + + +lookup_index_name <- function(mapping, index, label) { + key <- as.character(as.integer(index)) + if (!key %in% names(mapping)) { + cli::cli_abort(c( + "x" = "Unknown {label} index {.val {index}} in model DSL lowering." + )) + } + + unname(mapping[[key]]) +} + + +route_mapping_key <- function(kind, index) { + paste(tolower(kind), as.integer(index), sep = ":") +} + + +build_route_name_map <- function(routes) { + if (length(routes) == 0) { + return(setNames(character(0), character(0))) + } + + route_inputs <- vapply(routes, \(route) as.character(as.integer(route$input)), character(1)) + input_counts <- table(route_inputs) + route_names <- vapply(routes, \(route) { + input <- as.integer(route$input) + input_key <- as.character(input) + if (input_counts[[input_key]] > 1L) { + sprintf("input_%s_%s", input, route$kind) + } else { + sprintf("input_%s", input) + } + }, character(1)) + + names(route_names) <- vapply(routes, \(route) route_mapping_key(route$kind, route$input), character(1)) + route_names +} + + +lookup_route_name <- function(mapping, index, kind) { + key <- route_mapping_key(kind, index) + if (!key %in% names(mapping)) { + cli::cli_abort(c( + "x" = "Unknown {kind} input index {.val {index}} in model DSL lowering." + )) + } + + unname(mapping[[key]]) +} + + +lookup_symbol_name <- function(mappings, symbol_name) { + symbol_name <- tolower(symbol_name) + symbol_map <- mappings$symbols %||% character(0) + + if (symbol_name %in% names(symbol_map)) { + return(unname(symbol_map[[symbol_name]])) + } + + symbol_name +} + + +rename_declared_names <- function(names, symbol_map) { + if (length(names) == 0 || length(symbol_map) == 0) { + return(names) + } + + unique(vapply(names, function(name) { + name <- tolower(name) + if (name %in% names(symbol_map)) { + return(unname(symbol_map[[name]])) + } + + name + }, character(1))) +} + + +pm_model_route_mapping <- function(model) { + kind <- tolower(model$model_list$type) + + routes <- if (kind == "ode") { + state_names <- setNames( + paste0("state_", seq_len(model$model_list$n_eqn)), + as.character(seq_len(model$model_list$n_eqn)) + ) + collect_route_usage(statement_exprs(model$arg_list$eqn), state_names) + } else if (kind == "analytical") { + dsl_analytical_spec(model$model_list$name)$routes + } else { + cli::cli_abort(c("x" = "Unsupported model kind for route lowering: {.val {model$model_list$type}}.")) + } + + list(routes = routes, names = build_route_name_map(routes)) +} + + +write_runtime_data_matrix <- function(data, file_path) { + data <- as.data.frame(data, stringsAsFactors = FALSE) + os_type <- getOS() + eol <- c("\r\n", "\n", "\r\n")[os_type] + output <- file(file_path, "w") + on.exit(close(output), add = TRUE) + + writeLines(toupper(names(data)[-ncol(data)]), output, sep = getPMoptions("sep")) + writeLines(toupper(names(data)[ncol(data)]), output) + utils::write.table( + data, + output, + row.names = FALSE, + na = ".", + quote = FALSE, + sep = getPMoptions("sep"), + dec = getPMoptions("dec"), + col.names = FALSE, + eol = eol + ) + + invisible(file_path) +} + + +rewrite_runtime_route_labels <- function(file_path, model) { + route_mapping <- pm_model_route_mapping(model)$names + if (length(route_mapping) == 0 || !file.exists(file_path)) { + return(invisible(file_path)) + } + + runtime_data <- PMreadMatrix(file_path, quiet = TRUE) + + if (!all(c("evid", "dur", "input") %in% names(runtime_data))) { + return(invisible(file_path)) + } + + evid <- suppressWarnings(as.numeric(runtime_data$evid)) + dur <- suppressWarnings(as.numeric(runtime_data$dur)) + input_labels <- as.character(runtime_data$input) + dose_rows <- !is.na(evid) & evid != 0 & !is.na(input_labels) + + for (row in which(dose_rows)) { + input_index <- suppressWarnings(as.integer(as.numeric(input_labels[[row]]))) + if (is.na(input_index)) { + next + } + + route_kind <- if (!is.na(dur[[row]]) && dur[[row]] > 0) "infusion" else "bolus" + route_key <- route_mapping_key(route_kind, input_index) + if (route_key %in% names(route_mapping)) { + runtime_data$input[[row]] <- route_mapping[[route_key]] + } + } + + write_runtime_data_matrix(runtime_data, file_path) + invisible(file_path) +} + + +replace_route_refs <- function(expr) { + if (!is.call(expr)) { + return(expr) + } + + if (identical(expr[[1]], as.name("["))) { + target <- tolower(as.character(expr[[2]])) + if (target %in% c("b", "bolus", "r", "rateiv")) { + return(0) + } + } + + as.call(lapply(as.list(expr), replace_route_refs)) +} + + +format_dsl_number <- function(value) { + format(value, scientific = FALSE, trim = TRUE, digits = 15) +} + + +new_pm_dsl <- function(source) { + structure(as.character(source), class = c("pm_dsl", "character")) +} + + +#' @method print pm_dsl +#' @keywords internal +#' @noRd +#' @export +print.pm_dsl <- function(x, ...) { + writeLines(unclass(x)) + invisible(x) +} + + +is_numeric_literal <- function(expr, value = NULL) { + if (!is.numeric(expr) || length(expr) != 1) { + return(FALSE) + } + + if (is.null(value)) { + return(TRUE) + } + + isTRUE(all.equal(as.numeric(expr), as.numeric(value))) +} + + +is_zero_literal <- function(expr) { + is_numeric_literal(expr, 0) +} + + +is_one_literal <- function(expr) { + is_numeric_literal(expr, 1) +} + + +simplify_dsl_expr <- function(expr) { + if (!is.call(expr)) { + return(expr) + } + + op <- as.character(expr[[1]]) + args <- lapply(as.list(expr[-1]), simplify_dsl_expr) + + if (op == "(") { + return(args[[1]]) + } + + if (op == "+") { + if (length(args) == 1) { + return(args[[1]]) + } + + lhs <- args[[1]] + rhs <- args[[2]] + if (is_zero_literal(lhs)) { + return(rhs) + } + if (is_zero_literal(rhs)) { + return(lhs) + } + + return(as.call(list(as.name("+"), lhs, rhs))) + } + + if (op == "-") { + if (length(args) == 1) { + value <- args[[1]] + if (is_zero_literal(value)) { + return(0) + } + + return(as.call(list(as.name("-"), value))) + } + + lhs <- args[[1]] + rhs <- args[[2]] + if (is_zero_literal(rhs)) { + return(lhs) + } + if (is_zero_literal(lhs)) { + return(as.call(list(as.name("-"), rhs))) + } + + return(as.call(list(as.name("-"), lhs, rhs))) + } + + if (op == "*") { + lhs <- args[[1]] + rhs <- args[[2]] + if (is_zero_literal(lhs) || is_zero_literal(rhs)) { + return(0) + } + if (is_one_literal(lhs)) { + return(rhs) + } + if (is_one_literal(rhs)) { + return(lhs) + } + + return(as.call(list(as.name("*"), lhs, rhs))) + } + + if (op == "/") { + lhs <- args[[1]] + rhs <- args[[2]] + if (is_zero_literal(lhs)) { + return(0) + } + if (is_one_literal(rhs)) { + return(lhs) + } + + return(as.call(list(as.name("/"), lhs, rhs))) + } + + if (op == "^") { + lhs <- args[[1]] + rhs <- args[[2]] + if (is_one_literal(rhs)) { + return(lhs) + } + if (is_zero_literal(rhs)) { + return(1) + } + + return(as.call(list(as.name("^"), lhs, rhs))) + } + + as.call(c(expr[[1]], args)) +} + + +dsl_operator_precedence <- function(expr) { + if (!is.call(expr)) { + return(Inf) + } + + op <- as.character(expr[[1]]) + switch(op, + "if" = 1L, + "ifelse" = 1L, + "|" = 2L, + "||" = 2L, + "&" = 3L, + "&&" = 3L, + "==" = 4L, + "!=" = 4L, + ">=" = 4L, + "<=" = 4L, + ">" = 4L, + "<" = 4L, + "+" = if (length(expr) == 2) 7L else 5L, + "-" = if (length(expr) == 2) 7L else 5L, + "*" = 6L, + "/" = 6L, + "!" = 7L, + "^" = 8L, + "(" = 9L, + Inf + ) +} + + +needs_dsl_parentheses <- function(expr, parent_precedence, side = "left", parent_op = NULL) { + if (!is.call(expr)) { + return(FALSE) + } + + child_precedence <- dsl_operator_precedence(expr) + if (is.infinite(child_precedence)) { + return(FALSE) + } + + if (child_precedence < parent_precedence) { + return(TRUE) + } + + if (child_precedence > parent_precedence) { + return(FALSE) + } + + if (side == "right" && parent_op %in% c("-", "/", "^")) { + return(TRUE) + } + + if (side == "left" && identical(parent_op, "^")) { + return(TRUE) + } + + FALSE +} + + +render_dsl_child <- function(expr, mappings, parent_precedence, side = "left", parent_op = NULL) { + expr <- simplify_dsl_expr(expr) + text <- expr_to_dsl(expr, mappings) + + if (needs_dsl_parentheses(expr, parent_precedence, side = side, parent_op = parent_op)) { + return(sprintf("(%s)", text)) + } + + text +} + + +expr_to_dsl <- function(expr, mappings) { + expr <- simplify_dsl_expr(expr) + + if (is.null(expr)) { + return("") + } + + if (is.logical(expr) && length(expr) == 1) { + return(tolower(as.character(expr))) + } + + if (is.numeric(expr) && length(expr) == 1) { + return(format_dsl_number(expr)) + } + + if (is.symbol(expr)) { + return(lookup_symbol_name(mappings, as.character(expr))) + } + + if (!is.call(expr)) { + cli::cli_abort(c( + "x" = "Unsupported expression in DSL lowering.", + "i" = expr_text(expr) + )) + } + + if (identical(expr[[1]], as.name("["))) { + target <- tolower(as.character(expr[[2]])) + index <- expr[[3]] + + if (!is.numeric(index) || length(index) != 1) { + cli::cli_abort(c( + "x" = "Dynamic indices are not supported in DSL lowering.", + "i" = expr_text(expr) + )) + } + + index <- as.integer(index) + return(switch(target, + x = lookup_index_name(mappings$states, index, "state"), + y = lookup_index_name(mappings$outputs, index, "output"), + lag = sprintf("lag(%s)", lookup_route_name(mappings$routes, index, "bolus")), + fa = sprintf("fa(%s)", lookup_route_name(mappings$routes, index, "bolus")), + r = sprintf("rate(%s)", lookup_route_name(mappings$routes, index, "infusion")), + rateiv = sprintf("rate(%s)", lookup_route_name(mappings$routes, index, "infusion")), + b = cli::cli_abort(c("x" = "Bolus inputs must be lowered as routes, not inline expressions.")), + bolus = cli::cli_abort(c("x" = "Bolus inputs must be lowered as routes, not inline expressions.")), + sprintf("%s[%s]", target, index) + )) + } + + op <- as.character(expr[[1]]) + args <- as.list(expr[-1]) + + switch(op, + "(" = expr_to_dsl(args[[1]], mappings), + "+" = if (length(args) == 1) { + expr_to_dsl(args[[1]], mappings) + } else { + precedence <- 5L + sprintf( + "%s + %s", + render_dsl_child(args[[1]], mappings, precedence, side = "left", parent_op = op), + render_dsl_child(args[[2]], mappings, precedence, side = "right", parent_op = op) + ) + }, + "-" = if (length(args) == 1) { + precedence <- 7L + sprintf("-%s", render_dsl_child(args[[1]], mappings, precedence, side = "right", parent_op = op)) + } else { + precedence <- 5L + sprintf( + "%s - %s", + render_dsl_child(args[[1]], mappings, precedence, side = "left", parent_op = op), + render_dsl_child(args[[2]], mappings, precedence, side = "right", parent_op = op) + ) + }, + "*" = { + precedence <- 6L + sprintf( + "%s * %s", + render_dsl_child(args[[1]], mappings, precedence, side = "left", parent_op = op), + render_dsl_child(args[[2]], mappings, precedence, side = "right", parent_op = op) + ) + }, + "/" = { + precedence <- 6L + sprintf( + "%s / %s", + render_dsl_child(args[[1]], mappings, precedence, side = "left", parent_op = op), + render_dsl_child(args[[2]], mappings, precedence, side = "right", parent_op = op) + ) + }, + "^" = sprintf( + "pow(%s, %s)", + render_dsl_child(args[[1]], mappings, 8L, side = "left", parent_op = op), + render_dsl_child(args[[2]], mappings, 8L, side = "right", parent_op = op) + ), + "==" = sprintf("%s == %s", expr_to_dsl(args[[1]], mappings), expr_to_dsl(args[[2]], mappings)), + "!=" = sprintf("%s != %s", expr_to_dsl(args[[1]], mappings), expr_to_dsl(args[[2]], mappings)), + ">=" = sprintf("%s >= %s", expr_to_dsl(args[[1]], mappings), expr_to_dsl(args[[2]], mappings)), + "<=" = sprintf("%s <= %s", expr_to_dsl(args[[1]], mappings), expr_to_dsl(args[[2]], mappings)), + ">" = sprintf("%s > %s", expr_to_dsl(args[[1]], mappings), expr_to_dsl(args[[2]], mappings)), + "<" = sprintf("%s < %s", expr_to_dsl(args[[1]], mappings), expr_to_dsl(args[[2]], mappings)), + "&" = sprintf("%s && %s", expr_to_dsl(args[[1]], mappings), expr_to_dsl(args[[2]], mappings)), + "&&" = sprintf("%s && %s", expr_to_dsl(args[[1]], mappings), expr_to_dsl(args[[2]], mappings)), + "|" = sprintf("%s || %s", expr_to_dsl(args[[1]], mappings), expr_to_dsl(args[[2]], mappings)), + "||" = sprintf("%s || %s", expr_to_dsl(args[[1]], mappings), expr_to_dsl(args[[2]], mappings)), + "!" = sprintf("!%s", render_dsl_child(args[[1]], mappings, 7L, side = "right", parent_op = op)), + "abs" = sprintf("abs(%s)", expr_to_dsl(args[[1]], mappings)), + "ln" = sprintf("log(%s)", expr_to_dsl(args[[1]], mappings)), + "log" = sprintf("log(%s)", expr_to_dsl(args[[1]], mappings)), + "log10" = sprintf("log10(%s)", expr_to_dsl(args[[1]], mappings)), + "log2" = sprintf("log2(%s)", expr_to_dsl(args[[1]], mappings)), + "exp" = sprintf("exp(%s)", expr_to_dsl(args[[1]], mappings)), + "sqrt" = sprintf("sqrt(%s)", expr_to_dsl(args[[1]], mappings)), + "sin" = sprintf("sin(%s)", expr_to_dsl(args[[1]], mappings)), + "cos" = sprintf("cos(%s)", expr_to_dsl(args[[1]], mappings)), + "tan" = sprintf("tan(%s)", expr_to_dsl(args[[1]], mappings)), + "asin" = sprintf("asin(%s)", expr_to_dsl(args[[1]], mappings)), + "acos" = sprintf("acos(%s)", expr_to_dsl(args[[1]], mappings)), + "atan" = sprintf("atan(%s)", expr_to_dsl(args[[1]], mappings)), + "atan2" = sprintf("atan2(%s, %s)", expr_to_dsl(args[[1]], mappings), expr_to_dsl(args[[2]], mappings)), + "sinh" = sprintf("sinh(%s)", expr_to_dsl(args[[1]], mappings)), + "cosh" = sprintf("cosh(%s)", expr_to_dsl(args[[1]], mappings)), + "tanh" = sprintf("tanh(%s)", expr_to_dsl(args[[1]], mappings)), + "asinh" = sprintf("asinh(%s)", expr_to_dsl(args[[1]], mappings)), + "acosh" = sprintf("acosh(%s)", expr_to_dsl(args[[1]], mappings)), + "atanh" = sprintf("atanh(%s)", expr_to_dsl(args[[1]], mappings)), + "floor" = sprintf("floor(%s)", expr_to_dsl(args[[1]], mappings)), + "ceiling" = sprintf("ceil(%s)", expr_to_dsl(args[[1]], mappings)), + "round" = sprintf("round(%s)", expr_to_dsl(args[[1]], mappings)), + "trunc" = sprintf("trunc(%s)", expr_to_dsl(args[[1]], mappings)), + "if" = { + else_branch <- if (length(args) >= 3) expr_to_dsl(args[[3]], mappings) else "0" + sprintf("if (%s) %s else %s", expr_to_dsl(args[[1]], mappings), expr_to_dsl(args[[2]], mappings), else_branch) + }, + "ifelse" = sprintf("if (%s) %s else %s", expr_to_dsl(args[[1]], mappings), expr_to_dsl(args[[2]], mappings), expr_to_dsl(args[[3]], mappings)), + sprintf("%s(%s)", tolower(op), paste(vapply(args, expr_to_dsl, character(1), mappings = mappings), collapse = ", ")) + ) +} + + +indent_lines <- function(lines, spaces = 2) { + if (length(lines) == 0) { + return(character(0)) + } + + prefix <- strrep(" ", spaces) + paste0(prefix, lines) +} + + +translate_statement_block <- function(expr, context, mappings) { + if (is.call(expr) && identical(expr[[1]], as.name("{"))) { + return(unlist(lapply(as.list(expr[-1]), translate_statement, context = context, mappings = mappings), use.names = FALSE)) + } + + translate_statement(expr, context = context, mappings = mappings) +} + + +translate_statement <- function(expr, context, mappings) { + if (is.symbol(expr)) { + return(character(0)) + } + + if (!is.call(expr)) { + cli::cli_abort(c( + "x" = "Unsupported statement in DSL lowering.", + "i" = expr_text(expr) + )) + } + + op <- as.character(expr[[1]]) + + if (op == "{") { + return(translate_statement_block(expr, context = context, mappings = mappings)) + } + + if (op == "if") { + condition <- expr_to_dsl(expr[[2]], mappings) + then_lines <- translate_statement_block(expr[[3]], context = context, mappings = mappings) + else_lines <- if (length(expr) >= 4) { + translate_statement_block(expr[[4]], context = context, mappings = mappings) + } else { + character(0) + } + + return(c( + sprintf("if (%s) {", condition), + indent_lines(then_lines), + if (length(else_lines) > 0) { + c("} else {", indent_lines(else_lines), "}") + } else { + "}" + } + )) + } + + if (op == "for") { + cli::cli_abort(c( + "x" = "For loops are not supported in DSL lowering.", + "i" = expr_text(expr) + )) + } + + if (!op %in% c("<-", "=")) { + cli::cli_abort(c( + "x" = "Unsupported statement in DSL lowering.", + "i" = expr_text(expr) + )) + } + + lhs <- expr[[2]] + rhs <- expr[[3]] + + if (is.symbol(lhs)) { + return(sprintf("%s = %s", lookup_symbol_name(mappings, as.character(lhs)), expr_to_dsl(rhs, mappings))) + } + + if (!is.call(lhs) || !identical(lhs[[1]], as.name("["))) { + cli::cli_abort(c( + "x" = "Unsupported assignment target in DSL lowering.", + "i" = expr_text(expr) + )) + } + + target <- tolower(as.character(lhs[[2]])) + index <- lhs[[3]] + if (!is.numeric(index) || length(index) != 1) { + cli::cli_abort(c( + "x" = "Dynamic indices are not supported in DSL lowering.", + "i" = expr_text(expr) + )) + } + + index <- as.integer(index) + switch(context, + ode = { + if (target != "dx") { + cli::cli_abort(c("x" = "Only {.code dx[i]} assignments are allowed in the ODE block after DSL lowering.")) + } + + return(sprintf( + "dx(%s) = %s", + lookup_index_name(mappings$states, index, "state"), + expr_to_dsl(replace_route_refs(rhs), mappings) + )) + }, + derive = cli::cli_abort(c("x" = "Indexed assignments are not allowed in the derived block after DSL lowering.")), + ini = { + if (target != "x") { + cli::cli_abort(c("x" = "Only {.code x[i]} assignments are allowed in the initial conditions block after DSL lowering.")) + } + + return(sprintf( + "init(%s) = %s", + lookup_index_name(mappings$states, index, "state"), + expr_to_dsl(rhs, mappings) + )) + }, + lag = { + if (target != "lag") { + cli::cli_abort(c("x" = "Only {.code lag[i]} assignments are allowed in the lag block after DSL lowering.")) + } + + return(sprintf( + "lag(%s) = %s", + lookup_route_name(mappings$routes, index, "bolus"), + expr_to_dsl(rhs, mappings) + )) + }, + fa = { + if (target != "fa") { + cli::cli_abort(c("x" = "Only {.code fa[i]} assignments are allowed in the bioavailability block after DSL lowering.")) + } + + return(sprintf( + "fa(%s) = %s", + lookup_route_name(mappings$routes, index, "bolus"), + expr_to_dsl(rhs, mappings) + )) + }, + out = { + if (target != "y") { + cli::cli_abort(c("x" = "Only {.code y[i]} assignments are allowed in the output block after DSL lowering.")) + } + + return(sprintf( + "out(%s) = %s ~ continuous()", + lookup_index_name(mappings$outputs, index, "output"), + expr_to_dsl(rhs, mappings) + )) + }, + cli::cli_abort(c("x" = "Unknown DSL lowering context: {.val {context}}.")) + ) +} + + +translate_statements <- function(fun_or_exprs, context, mappings) { + exprs <- if (is.list(fun_or_exprs) && !is.function(fun_or_exprs)) { + fun_or_exprs + } else { + statement_exprs(fun_or_exprs) + } + + lines <- unlist(lapply(exprs, translate_statement, context = context, mappings = mappings), use.names = FALSE) + lines[nzchar(lines)] +} + + +build_route_declarations <- function(route_usage, route_names) { + if (length(route_usage) == 0) { + return(character(0)) + } + + purrr::map_chr(route_usage, \(route) { + sprintf( + "%s(%s) -> %s", + route$kind, + lookup_route_name(route_names, route$input, route$kind), + route$destination + ) + }) +} + + +model_derived_names <- function(arg_list, eqn_exprs, excluded_names) { + derived <- unique(c( + collect_assigned_names(arg_list$sec), + collect_assigned_names_from_exprs(eqn_exprs), + collect_assigned_names(arg_list$ini), + collect_assigned_names(arg_list$lag), + collect_assigned_names(arg_list$fa), + collect_assigned_names(arg_list$out) + )) + + derived[!derived %in% excluded_names] +} + + +pm_model_to_dsl_source <- function(model) { + if (!inherits(model, "PM_model")) { + cli::cli_abort(c("x" = "DSL lowering requires a {.cls PM_model} object.")) + } + + if (is.null(model$model_list)) { + cli::cli_abort(c("x" = "Model metadata is empty.", "i" = "Please create a valid {.cls PM_model} first.")) + } + + kind <- tolower(model$model_list$type) + params <- tolower(model$model_list$parameters) + covariate_names <- tolower(names(model$arg_list$cov %||% list())) + covariate_declarations <- dsl_covariate_declarations(model$arg_list$cov) + output_indices <- get_assignment_indices(model$arg_list$out, "y") + output_names <- setNames(paste0("outeq_", output_indices), as.character(output_indices)) + + if (length(output_names) == 0) { + cli::cli_abort(c("x" = "Model DSL lowering requires at least one output equation.")) + } + + if (kind == "ode") { + state_names <- setNames( + paste0("state_", seq_len(model$model_list$n_eqn)), + as.character(seq_len(model$model_list$n_eqn)) + ) + eqn_exprs <- statement_exprs(model$arg_list$eqn) + route_info <- pm_model_route_mapping(model) + route_usage <- route_info$routes + route_names <- route_info$names + excluded_names <- c(params, covariate_names, unname(state_names), unname(output_names)) + derived_names <- model_derived_names(model$arg_list, eqn_exprs, excluded_names) + mappings <- list(states = state_names, outputs = output_names, routes = route_names) + + lines <- c( + "name = user", + "kind = ode", + sprintf("params = %s", paste(params, collapse = ", ")), + if (length(covariate_declarations) > 0) sprintf("covariates = %s", paste(covariate_declarations, collapse = ", ")), + if (length(derived_names) > 0) sprintf("derived = %s", paste(derived_names, collapse = ", ")), + sprintf("states = %s", paste(unname(state_names), collapse = ", ")), + sprintf("outputs = %s", paste(unname(output_names), collapse = ", ")), + build_route_declarations(route_usage, route_names), + translate_statements(model$arg_list$sec, "derive", mappings), + translate_statements(model$arg_list$ini, "ini", mappings), + translate_statements(model$arg_list$lag, "lag", mappings), + translate_statements(model$arg_list$fa, "fa", mappings), + translate_statements(eqn_exprs, "ode", mappings), + translate_statements(model$arg_list$out, "out", mappings) + ) + } else if (kind == "analytical") { + spec <- dsl_analytical_spec(model$model_list$name) + state_names <- setNames(spec$states, as.character(seq_along(spec$states))) + route_info <- pm_model_route_mapping(model) + route_names <- route_info$names + eqn_exprs <- statement_exprs(model$arg_list$eqn) + eqn_exprs <- purrr::discard(eqn_exprs, \(expr) is_template_statement(expr, model$model_list$name)) + runtime_params <- tolower(spec$params %||% params) + symbol_map <- spec$symbols %||% character(0) + if (length(symbol_map) > 0) { + symbol_map <- stats::setNames(tolower(unname(symbol_map)), tolower(names(symbol_map))) + } + excluded_names <- c(runtime_params, covariate_names, unname(state_names), unname(output_names)) + derived_names <- model_derived_names(model$arg_list, eqn_exprs, excluded_names) + derived_names <- rename_declared_names(derived_names, symbol_map) + mappings <- list(states = state_names, outputs = output_names, routes = route_names, symbols = symbol_map) + + lines <- c( + sprintf("name = %s", tolower(model$model_list$name)), + "kind = analytical", + sprintf("params = %s", paste(runtime_params, collapse = ", ")), + if (length(covariate_declarations) > 0) sprintf("covariates = %s", paste(covariate_declarations, collapse = ", ")), + if (length(derived_names) > 0) sprintf("derived = %s", paste(derived_names, collapse = ", ")), + sprintf("states = %s", paste(unname(state_names), collapse = ", ")), + sprintf("outputs = %s", paste(unname(output_names), collapse = ", ")), + build_route_declarations(spec$routes, route_names), + translate_statements(model$arg_list$sec, "derive", mappings), + translate_statements(eqn_exprs, "derive", mappings), + sprintf("structure = %s", spec$structure), + translate_statements(model$arg_list$ini, "ini", mappings), + translate_statements(model$arg_list$lag, "lag", mappings), + translate_statements(model$arg_list$fa, "fa", mappings), + translate_statements(model$arg_list$out, "out", mappings) + ) + } else { + cli::cli_abort(c("x" = "Unsupported model type for DSL lowering: {.val {model$model_list$type}}.")) + } + + paste(lines[nzchar(lines)], collapse = "\n") +} diff --git a/R/model_transpiler.R b/R/model_transpiler.R deleted file mode 100755 index eebf90b91..000000000 --- a/R/model_transpiler.R +++ /dev/null @@ -1,594 +0,0 @@ -# R-to-Rust transpiler for Pmetrics model blocks. - -index_vector_size <- function(max_index) { - if (max_index <= 0L) { - return(1L) - } - - max_index + 1L -} - -get_max_index <- function(fn_or_expr, targets) { - targets <- tolower(targets) - - walk_expr <- function(expr) { - if (is.call(expr) && identical(expr[[1]], as.name("["))) { - target_name <- tolower(as.character(expr[[2]])) - if (target_name %in% targets) { - idx <- expr[[3]] - if (is.numeric(idx) && length(idx) == 1) { - return(as.integer(idx)) - } - } - } - - if (is.call(expr)) { - return(max(vapply(as.list(expr), walk_expr, integer(1)), 0L)) - } - - 0L - } - - expr <- if (is.function(fn_or_expr)) body(fn_or_expr) else fn_or_expr - walk_expr(expr) -} - -get_assignment_indices <- function(fn_or_expr, targets) { - targets <- tolower(targets) - found <- integer(0) - - walk_expr <- function(expr) { - if (!is.call(expr)) { - return(invisible(NULL)) - } - - if (identical(expr[[1]], as.name("<-")) || identical(expr[[1]], as.name("="))) { - lhs <- expr[[2]] - if (is.call(lhs) && identical(lhs[[1]], as.name("["))) { - target_name <- tolower(as.character(lhs[[2]])) - idx <- lhs[[3]] - if (target_name %in% targets && is.numeric(idx) && length(idx) == 1) { - found <<- c(found, as.integer(idx)) - } - } - } - - lapply(as.list(expr), walk_expr) - invisible(NULL) - } - - expr <- if (is.function(fn_or_expr)) body(fn_or_expr) else fn_or_expr - walk_expr(expr) - sort(unique(found)) -} - -get_max_assignment_index <- function(fn_or_expr, targets) { - found <- get_assignment_indices(fn_or_expr, targets) - if (length(found) == 0) { - return(0L) - } - max(found) -} - -has_nonliteral_index <- function(fn_or_expr, targets) { - targets <- tolower(targets) - - walk_expr <- function(expr) { - if (is.call(expr) && identical(expr[[1]], as.name("["))) { - target_name <- tolower(as.character(expr[[2]])) - if (target_name %in% targets) { - idx <- expr[[3]] - return(!(is.numeric(idx) && length(idx) == 1)) - } - } - - if (is.call(expr)) { - return(any(vapply(as.list(expr), walk_expr, logical(1)))) - } - - FALSE - } - - expr <- if (is.function(fn_or_expr)) body(fn_or_expr) else fn_or_expr - walk_expr(expr) -} - -# Convert an R expression to Rust code (recursive) -expr_to_rust <- function( - expr, params = NULL, covs = NULL, - declared = new.env(parent = emptyenv()) -) { - declared_has <- function(name) isTRUE(get0(name, envir = declared, inherits = FALSE)) - declared_add <- function(name) assign(name, TRUE, envir = declared) - - # Base cases: numeric literals as floats, symbols - if (is.numeric(expr) && length(expr) == 1) { - val <- expr - if (val == floor(val)) { - return(sprintf("%d.0", as.integer(val))) - } else { - return(as.character(val)) - } - } - if (is.symbol(expr)) { - return(as.character(expr)) - } - - # Handle indexing. - if (is.call(expr) && as.character(expr[[1]]) == "[") { - var <- as.character(expr[[2]]) - idx_raw <- expr[[3]] - if (is.numeric(idx_raw) && length(idx_raw) == 1) { - return(sprintf("%s[%d]", var, as.integer(idx_raw))) - } else { - idx_code <- expr_to_rust(idx_raw, params, covs, declared) - return(sprintf("%s[%s]", var, idx_code)) - } - } - - if (!is.call(expr)) stop("Unknown expression type") - op <- as.character(expr[[1]]) - args <- as.list(expr[-1]) - rust_args <- lapply(args, expr_to_rust, - params = params, covs = covs, - declared = declared - ) - switch(op, - # Grouping - "(" = sprintf("(%s)", rust_args[[1]]), - "{" = { - inner <- if (length(args) == 0) { - character(0) - } else { - # turn each inner expr into a statement, joined by newlines - inner_exprs <- as.list(args) - paste( - vapply(inner_exprs, function(e) { - expr_to_rust(e, params, covs, declared) - }, character(1)), - collapse = "\n" - ) - } - inner - }, - - # Arithmetic - "+" = if (length(rust_args) == 1) { - sprintf("+(%s)", rust_args[[1]]) - } else { - sprintf("%s + %s", rust_args[[1]], rust_args[[2]]) - }, - "-" = if (length(rust_args) == 1) { - sprintf("-(%s)", rust_args[[1]]) - } else { - sprintf("(%s) - (%s)", rust_args[[1]], rust_args[[2]]) - }, - "*" = sprintf("%s * %s", rust_args[[1]], rust_args[[2]]), - "/" = sprintf("%s / %s", rust_args[[1]], rust_args[[2]]), - "^" = if (suppressWarnings(!is.na(as.numeric(rust_args[[1]])))) { - sprintf("(%sf64).powf(%s)", rust_args[[1]], rust_args[[2]]) - } else { - sprintf("(%s).powf(%s)", rust_args[[1]], rust_args[[2]]) - }, - - # Comparison - "==" = sprintf("%s == %s", rust_args[[1]], rust_args[[2]]), - "!=" = sprintf("%s != %s", rust_args[[1]], rust_args[[2]]), - ">=" = sprintf("%s >= %s", rust_args[[1]], rust_args[[2]]), - "<=" = sprintf("%s <= %s", rust_args[[1]], rust_args[[2]]), - ">" = sprintf("%s > %s", rust_args[[1]], rust_args[[2]]), - "<" = sprintf("%s < %s", rust_args[[1]], rust_args[[2]]), - - # Logical - "&" = sprintf("%s && %s", rust_args[[1]], rust_args[[2]]), - "|" = sprintf("%s || %s", rust_args[[1]], rust_args[[2]]), - "!" = sprintf("!(%s)", rust_args[[1]]), - - # Math funcs - "abs" = sprintf("(%s).abs()", rust_args[[1]]), - "ln" = sprintf("(%s).ln()", rust_args[[1]]), - "log" = sprintf("(%s).ln()", rust_args[[1]]), - "log10" = sprintf("(%s).log10()", rust_args[[1]]), - "log2" = sprintf("(%s).log2()", rust_args[[1]]), - "exp" = sprintf("(%s).exp()", rust_args[[1]]), - "sqrt" = sprintf("(%s).sqrt()", rust_args[[1]]), - - # Trigonometric - "sin" = sprintf("(%s).sin()", rust_args[[1]]), - "cos" = sprintf("(%s).cos()", rust_args[[1]]), - "tan" = sprintf("(%s).tan()", rust_args[[1]]), - "asin" = sprintf("(%s).asin()", rust_args[[1]]), - "acos" = sprintf("(%s).acos()", rust_args[[1]]), - "atan" = sprintf("(%s).atan()", rust_args[[1]]), - "atan2" = sprintf("(%s).atan2(%s)", rust_args[[1]], rust_args[[2]]), - - # Hyperbolic - "sinh" = sprintf("(%s).sinh()", rust_args[[1]]), - "cosh" = sprintf("(%s).cosh()", rust_args[[1]]), - "tanh" = sprintf("(%s).tanh()", rust_args[[1]]), - "asinh" = sprintf("(%s).asinh()", rust_args[[1]]), - "acosh" = sprintf("(%s).acosh()", rust_args[[1]]), - "atanh" = sprintf("(%s).atanh()", rust_args[[1]]), - - # Rounding - "floor" = sprintf("(%s).floor()", rust_args[[1]]), - "ceiling" = sprintf("(%s).ceil()", rust_args[[1]]), - "round" = sprintf("(%s).round()", rust_args[[1]]), - "trunc" = sprintf("(%s).trunc()", rust_args[[1]]), - - # Assignment - "<-" = , - "=" = { - lhs <- args[[1]] - rhs_code <- rust_args[[2]] - - if (is.symbol(lhs)) { - name <- as.character(lhs) - if (declared_has(name)) { - sprintf("%s = %s;", name, rhs_code) - } else { - declared_add(name) - sprintf("let mut %s = %s;", name, rhs_code) - } - } else { - # e.g., dx[1] = ... - lhs_code <- expr_to_rust(lhs, params, covs, declared) - sprintf("%s = %s;", lhs_code, rhs_code) - } - }, - - - # If - "if" = { - cond <- rust_args[[1]] - then_code <- expr_to_rust(args[[2]], params, covs, declared) - if (length(args) == 3) { - else_code <- expr_to_rust(args[[3]], params, covs, declared) - sprintf("if %s { %s } else { %s };", cond, then_code, else_code) - } else { - sprintf("if %s { %s };", cond, then_code) - } - }, - - # For (left as-is; thread declared through) - "for" = { - var <- as.character(args[[1]]) - n_sym <- as.character(args[[2]][[3]]) - loop_exprs <- if (is.call(args[[3]]) && as.character(args[[3]][[1]]) == "{") { - as.list(args[[3]][-1]) - } else { - list(args[[3]]) - } - body <- stmts_to_rust(loop_exprs, params, covs) - sprintf("for %s in 0..%s as usize {\n%s}\n", var, n_sym, indent(body)) - }, - - # Pmetrics functions - "get_e2" = { - sprintf( - "get_e2(%s, %s, %s, %s, %s, %s);", - rust_args[[1]], rust_args[[2]], rust_args[[3]], - rust_args[[4]], rust_args[[5]], rust_args[[6]] - ) - }, - stop(sprintf("Unsupported operation: %s", op)) - ) -} - - -# Helpers: convert list of statements to Rust and indent blocks -stmts_to_rust <- function( - exprs, params = NULL, covs = NULL -) { - lines <- vapply( - exprs, - expr_to_rust, - character(1), - params = params, - covs = covs - ) - tolower(paste(lines, collapse = "\n")) # rust is case sensitive, make everything lowercase -} - -indent <- function(text, spaces = 4) { - prefix <- strrep(" ", spaces) - paste0(prefix, gsub("\n", paste0("\n", prefix), text)) -} - -# Transpile an R ODE function to Rust closure -transpile_ode_eqn <- function(fun, params, covs, sec) { - exprs <- if (is.call(body(fun)) && as.character(body(fun)[[1]]) == "{") as.list(body(fun)[-1]) else list(body(fun)) - header <- sprintf( - "|x, p, t, dx, b, rateiv, cov| {\n fetch_cov!(cov, t, %s);\n fetch_params!(p, %s); %s", - paste(covs, collapse = ", "), - paste(params, collapse = ", "), - paste(sec, collapse = ", ") - ) - body_rust <- stmts_to_rust(exprs, params, covs) |> - stringr::str_replace_all("bolus\\[", "b\\[") |> - stringr::str_replace_all("r\\[", "rateiv\\[") - sprintf("%s\n%s\n }", header, indent(body_rust, spaces = 4)) -} - - -transpile_analytic_eqn <- function( - fun, params, covs -) { - if (is.call(body(fun)) && as.character(body(fun)[[1]]) == "{") { - found <- get_found_model(fun) - if (length(found) == 1) { # NA - cli::cli_abort(c( - "x" = "No ODE and no library model templates found.", - "i" = "EQN block must contain ODEs or a single library model template name." - )) - } - tem <- found$name - eqns <- as.list(body(fun)[-1]) - if (length(eqns) > 0) { - eqns_char <- map_chr(eqns, \(x) paste(deparse(x, width.cutoff = 500L), collapse = "\n")) - # check for ODE, which should not be present - if (any(stringr::str_detect(eqns_char, regex("dx\\[\\d+\\]", ignore_case = TRUE)))) { - cli::cli_abort(c( - "x" = "You appear to have included both a model library template and ODE.", - "i" = "EQN block must contain ODEs or a single library model template name, not both." - )) - } - exprs <- eqns[-which(eqns_char == found$name)] - } else { - exprs <- NULL - } - } else { - list(body(fun)) - } - - # map model name from R to rust - rust_tem <- dplyr::case_when( - tem == "one_comp_iv" ~ "pm_one_compartment", - tem == "one_comp_iv_cl" ~ "pm_one_compartment_cl", - tem == "one_comp_bolus" ~ "pm_one_compartment_with_absorption", - tem == "one_comp_bolus_cl" ~ "pm_one_compartment_cl_with_absorption", - tem == "two_comp_iv" ~ "pm_two_compartments", - tem == "two_comp_iv_cl" ~ "pm_two_compartments_cl", - tem == "two_comp_bolus" ~ "pm_two_compartments_with_absorption", - tem == "two_comp_bolus_cl" ~ "pm_two_compartments_cl_with_absorption", - tem == "three_comp_iv" ~ "pm_three_compartments", - tem == "three_comp_iv_cl" ~ "pm_three_compartments_cl", - tem == "three_comp_bolus" ~ "pm_three_compartments_with_absorption", - tem == "three_comp_bolus_cl" ~ "pm_three_compartments_cl_with_absorption", - .default = "" - ) - if (rust_tem == "") { - cli::cli_abort(c( - "x" = "Model template not recognized or not supported in Rust.", - "i" = "See {.fn model_lib} for details on supported templates." - )) - } - - header <- sprintf( - " %s ,\n |p, t, cov| {\n fetch_cov!(cov, t, %s);\n fetch_params!(&p, %s);", - rust_tem, - paste(covs, collapse = ", "), - paste(params, collapse = ", ") - ) - body_rust <- stmts_to_rust(exprs) - # remap parameters - # req_par <- get(tem)$parameters |> - # tolower() |> - - - # this block needs to write the equations, e.g. p[0] = ke, based on the parameters in the model template, - # not the parameters in the model. The model parameters may have different names, but are checked earlier. - - req_par <- model_lib(show = FALSE) |> - filter(Name == tem) |> - select(Parameters) |> - stringr::str_split(", ", simplify = TRUE) |> - unlist() |> - tolower() |> - purrr::discard(~ .x == "v" & !tem %in% c("one_comp_iv_cl", "two_comp_bolus_cl")) |> # don't include V for models that don't need it in equations - purrr::imap_chr(\(x, y){ - sprintf("p[%i] = %s;", y - 1, x) - }) |> - paste(collapse = "\n") - - sprintf("%s\n%s\n%s\n }", header, indent(body_rust, spaces = 4), indent(req_par, spaces = 4)) -} - -transpile_sec <- function(fun) { - exprs <- if (is.call(body(fun)) && as.character(body(fun)[[1]]) == "{") as.list(body(fun)[-1]) else list(body(fun)) - body_rust <- stmts_to_rust(exprs) - sprintf("%s\n", indent(body_rust, spaces = 4)) -} - - -transpile_fa <- function( - fun, params, covs, sec -) { - exprs <- if (is.call(body(fun)) && as.character(body(fun)[[1]]) == "{") { - as.list(body(fun)[-1]) - } else { - list(body(fun)) - } - - find_max_idx <- function(expr) { - if (is.call(expr) && as.character(expr[[1]]) == "[" && - tolower(as.character(expr[[2]])) == "fa" && - is.numeric(expr[[3]])) { - return(as.integer(expr[[3]])) - } - if (is.call(expr)) { - return(max(sapply(as.list(expr), find_max_idx), 0L)) - } - 0L - } - max_fa <- max(sapply(exprs, find_max_idx), 0L) - arr_size <- index_vector_size(max_fa) - - header <- sprintf( - "|p, t, cov| {\nfetch_params!(p, %s);\nfetch_cov!(cov, t, %s);\nlet mut fa: [f64; %d] = [0.0; %d];\n%s", - paste(params, collapse = ", "), - paste(covs, collapse = ", "), - arr_size, arr_size, - if (length(sec)) paste0(" ", paste(sec, collapse = "\n "), "\n") else "" - ) - - body_lines <- stmts_to_rust(exprs, params = params, covs = covs) - - slots <- seq_len(arr_size) - 1L - slot_args <- paste0(slots, "=> fa[", slots, "]", collapse = ", ") - footer <- sprintf(" fa!{%s}}", slot_args) - - paste0(header, indent(body_lines, 2), "\n", footer) -} - - -transpile_lag <- function( - fun, params, covs, sec -) { - exprs <- if (is.call(body(fun)) && as.character(body(fun)[[1]]) == "{") { - as.list(body(fun)[-1]) - } else { - list(body(fun)) - } - - find_max_idx <- function(expr) { - if (is.call(expr) && as.character(expr[[1]]) == "[" && - tolower(as.character(expr[[2]])) == "lag" && - is.numeric(expr[[3]])) { - return(as.integer(expr[[3]])) - } - if (is.call(expr)) { - return(max(sapply(as.list(expr), find_max_idx), 0L)) - } - 0L - } - max_lag <- max(sapply(exprs, find_max_idx), 0L) - arr_size <- index_vector_size(max_lag) - - header <- sprintf( - "|p, t, cov| {\nfetch_params!(p, %s);\nfetch_cov!(cov, t, %s);\nlet mut lag: [f64; %d] = [0.0; %d];\n%s", - paste(params, collapse = ", "), - paste(covs, collapse = ", "), - arr_size, arr_size, - if (length(sec)) paste0(" ", paste(sec, collapse = "\n "), "\n") else "" - ) - - body_lines <- stmts_to_rust(exprs, params = params, covs = covs) - - slots <- seq_len(arr_size) - 1L - slot_args <- paste0(slots, "=> lag[", slots, "]", collapse = ", ") - footer <- sprintf(" lag!{%s}}", slot_args) - - paste0(header, indent(body_lines, 2), "\n", footer) -} - -transpile_ini <- function( - fun, params, covs, sec -) { - exprs <- if (is.call(body(fun)) && as.character(body(fun)[[1]]) == "{") as.list(body(fun)[-1]) else list(body(fun)) - header <- sprintf( - "|p, t, cov, x| {\n fetch_cov!(cov, t, %s);\n fetch_params!(p, %s); %s", - paste(covs, collapse = ", "), - paste(params, collapse = ", "), - paste(sec, collapse = ", ") - ) - body_rust <- stmts_to_rust(exprs, params, covs) - sprintf("%s\n%s\n }", header, indent(body_rust, spaces = 4)) -} - -transpile_out <- function( - fun, params, covs, sec -) { - exprs <- if (is.call(body(fun)) && as.character(body(fun)[[1]]) == "{") as.list(body(fun)[-1]) else list(body(fun)) - header <- sprintf( - "|x, p, t, cov, y| {\n fetch_cov!(cov, t, %s);\n fetch_params!(p, %s);\n%s", - paste(covs, collapse = ", "), - paste(params, collapse = ", "), - paste(sec, collapse = ", ") - ) - body_rust <- stmts_to_rust(exprs, params, covs) - sprintf("%s\n%s\n }", header, indent(body_rust, spaces = 4)) -} - -empty_sec <- function() { - "|_p, _t, _cov| { }" -} -empty_fa <- function() { - "|_p,_t,_cov| fa! {}" -} -empty_lag <- function() { - "|_p,_t,_cov| lag! {}" -} -empty_ini <- function() { - "|_p, _t, _cov, _x| { }" -} - -empty_out <- function() { - "|_x, _p, _t, _cov, _y| { }" -} - - -get_assignments <- function(fn, assign) { - count_assignments <- function(expr) { - if (is.call(expr)) { - if (identical(expr[[1]], as.name("<-")) || identical(expr[[1]], as.name("="))) { - lhs <- expr[[2]] - # Check if lhs is an indexing call (i.e., assign[...] <- ...) - if (is.call(lhs) && identical(lhs[[1]], as.name("["))) { - target_name <- as.character(lhs[[2]]) - if (tolower(target_name) == tolower(assign)) { - return(1 + count_assignments(expr[[3]])) - } - } - return(count_assignments(expr[[2]]) + count_assignments(expr[[3]])) - } else { - return(sum(sapply(expr, count_assignments))) - } - } - return(0) - } - - body_expr <- body(fn) - count_assignments(body_expr) -} - - -reserved_name_conflicts <- function(blocks) { - reserved <- c( - "t", - "x", - "dx", - "p", - "b", - "bolus", - "r", - "rateiv", - "cov", - "y" - ) - - conflicts <- purrr::map(blocks, \(b) { - purrr::map_chr(reserved, \(r) { - if (is.function(b)) { - b <- func_to_char(b) - } - if (is.list(b)) { - b <- names(b) - con_match <- stringr::str_detect(tolower(b), glue::glue("^\\b{r}\\b")) # stand alone - } else { - con_match <- stringr::str_detect(tolower(b), glue::glue("^{r}\\s+[=<]")) # assignment in function - } - - if (any(con_match)) { - return(r) - } else { - return(NA_character_) - } - }) - }) |> - unlist() |> - purrr::discard(\(d) is.na(d)) |> - unique() - - return(conflicts) -} diff --git a/inst/Examples/Rscript/sylvain.R b/inst/Examples/Rscript/sylvain.R new file mode 100644 index 000000000..1b88ddf72 --- /dev/null +++ b/inst/Examples/Rscript/sylvain.R @@ -0,0 +1,28 @@ +PM_model$new( + pri = list( + CL = ab(0.05, 2), + Q = ab(0, 2), + V1 = ab(2, 25), + V2 = ab(0.5, 20) + ), + cov = list( + AGE = interp("none"), + CREAT = interp("none"), + WEIGHT = interp(), + MALE = interp("none"), + CLCR = interp("none"), + OCC = interp("none") + ), + sec = function() { + Q <- 1.58 + }, + eqn = function() { + two_comp_iv_cl + }, + out = function() { + Y[1] <- X[1] / V1 + }, + err = list( + proportional(5, c(0.1, 0.1, 0.0, 0.0)) + ) +) diff --git a/inst/Examples/Rscript/sylvain_sim.R b/inst/Examples/Rscript/sylvain_sim.R new file mode 100644 index 000000000..f3c0e5e85 --- /dev/null +++ b/inst/Examples/Rscript/sylvain_sim.R @@ -0,0 +1,41 @@ +#!/usr/bin/env Rscript + +devtools::load_all() + +path <- "inst/Examples/Runs/sylvain_sim" + +dir.create(path, recursive = TRUE, showWarnings = FALSE) + +data <- PM_data$new( + "inst/Examples/src/Dapto_Lyon.csv", + quiet = TRUE +) + +model <- source( + "inst/Examples/Rscript/sylvain.R", + local = TRUE +)$value + +theta <- matrix( + c( + 0.35, 1.58, 9.0, 7.0, + 0.65, 1.58, 13.0, 11.0 + ), + nrow = 2, + byrow = TRUE, + dimnames = list(NULL, c("CL", "Q", "V1", "V2")) +) + +simulation <- model$sim( + data = data, + theta = theta +) + +utils::write.csv(theta, file.path(path, "theta.csv"), row.names = FALSE) +utils::write.csv(simulation, file.path(path, "simulation.csv"), row.names = FALSE) + +cat("Rows:", nrow(simulation), "\n") +cat("Theta:", file.path(path, "theta.csv"), "\n") +cat("Simulation:", file.path(path, "simulation.csv"), "\n") + +print(utils::head(simulation)) diff --git a/inst/Examples/Runs/sylvain_sim/simulation.csv b/inst/Examples/Runs/sylvain_sim/simulation.csv new file mode 100644 index 000000000..0d64ded63 --- /dev/null +++ b/inst/Examples/Runs/sylvain_sim/simulation.csv @@ -0,0 +1,3463 @@ +"id","time","out","outeq","state","state_index","spp_index" +"1",24.7,38.4683220155012,1,0,0,0 +"1",24.7,38.4683220155012,1,346.21489813951,1,0 +"1",24.7,38.4683220155012,1,296.790690713784,2,0 +"1",24.7,38.4683220155012,1,0,0,0 +"1",24.7,38.4683220155012,1,346.21489813951,1,0 +"1",24.7,38.4683220155012,1,296.790690713784,2,0 +"1",26.7,101.512829629594,1,0,0,0 +"1",26.7,101.512829629594,1,913.615466666347,1,0 +"1",26.7,101.512829629594,1,379.531123088507,2,0 +"1",28.7,146.377055968968,1,0,0,0 +"1",28.7,146.377055968968,1,1317.39350372072,1,0 +"1",28.7,146.377055968968,1,583.609155736788,2,0 +"1",31.7,114.960831958349,1,0,0,0 +"1",31.7,114.960831958349,1,1034.64748762514,1,0 +"1",31.7,114.960831958349,1,731.828255221734,2,0 +"1",33.3,106.720097204739,1,0,0,0 +"1",33.3,106.720097204739,1,960.480874842652,1,0 +"1",33.3,106.720097204739,1,744.077986991793,2,0 +"11",26.6,26.4052155465263,1,0,0,0 +"11",26.6,26.4052155465263,1,237.646939918737,1,0 +"11",26.6,26.4052155465263,1,203.729191184882,2,0 +"11",27.25,78.2787428746096,1,0,0,0 +"11",27.25,78.2787428746096,1,704.508685871486,1,0 +"11",27.25,78.2787428746096,1,225.286573629276,2,0 +"11",32,52.5350658565311,1,0,0,0 +"11",32,52.5350658565311,1,472.81559270878,1,0 +"11",32,52.5350658565311,1,353.648967164651,2,0 +"12",26.7,26.3499684593776,1,0,0,0 +"12",26.7,26.3499684593776,1,237.149716134398,1,0 +"12",26.7,26.3499684593776,1,203.303199588568,2,0 +"12",28.75,73.107417365274,1,0,0,0 +"12",28.75,73.107417365274,1,657.966756287466,1,0 +"12",28.75,73.107417365274,1,250.493442820576,2,0 +"12",34.25,49.5252704193154,1,0,0,0 +"12",34.25,49.5252704193154,1,445.727433773839,1,0 +"12",34.25,49.5252704193154,1,350.863995296728,2,0 +"13",24.3,38.7920932096527,1,0,0,0 +"13",24.3,38.7920932096527,1,349.128838886875,1,0 +"13",24.3,38.7920932096527,1,299.284946941775,2,0 +"13",24.75,38.4280435878291,1,0,0,0 +"13",24.75,38.4280435878291,1,345.852392290462,1,0 +"13",24.75,38.4280435878291,1,296.480353426105,2,0 +"13",25,38.2272907290519,1,0,0,0 +"13",25,38.2272907290519,1,344.045616561467,1,0 +"13",25,38.2272907290519,1,294.93346603592,2,0 +"13",27,135.912310488484,1,0,0,0 +"13",27,135.912310488484,1,1223.21079439636,1,0 +"13",27,135.912310488484,1,407.791292202562,2,0 +"13",27.75,195.697006541794,1,0,0,0 +"13",27.75,195.697006541794,1,1761.27305887614,1,0 +"13",27.75,195.697006541794,1,526.104544469464,2,0 +"13",28.5,211.180058924127,1,0,0,0 +"13",28.5,211.180058924127,1,1900.62053031714,1,0 +"13",28.5,211.180058924127,1,679.993854757363,2,0 +"13",32.2,152.784974238499,1,0,0,0 +"13",32.2,152.784974238499,1,1375.06476814649,1,0 +"13",32.2,152.784974238499,1,977.563110569773,2,0 +"13",32.75,148.570703521826,1,0,0,0 +"13",32.75,148.570703521826,1,1337.13633169643,1,0 +"13",32.75,148.570703521826,1,986.496004608931,2,0 +"13",34.5,138.355310552678,1,0,0,0 +"13",34.5,138.355310552678,1,1245.1977949741,1,0 +"13",34.5,138.355310552678,1,990.766955515464,2,0 +"14",21.9,29.1385356265914,1,0,0,0 +"14",21.9,29.1385356265914,1,262.246820639323,1,0 +"14",21.9,29.1385356265914,1,224.776537798573,2,0 +"14",22.9,77.3028693929257,1,0,0,0 +"14",22.9,77.3028693929257,1,695.725824536331,1,0 +"14",22.9,77.3028693929257,1,269.409530137889,2,0 +"14",24.2,114.992539672087,1,0,0,0 +"14",24.2,114.992539672087,1,1034.93285704878,1,0 +"14",24.2,114.992539672087,1,384.037292151115,2,0 +"14",24.25,114.248092994402,1,0,0,0 +"14",24.25,114.248092994402,1,1028.23283694961,1,0 +"14",24.25,114.248092994402,1,388.731476939169,2,0 +"14",28.1,82.9509288565787,1,0,0,0 +"14",28.1,82.9509288565787,1,746.558359709209,1,0 +"14",28.1,82.9509288565787,1,541.921421482389,2,0 +"14",29.75,77.259424323396,1,0,0,0 +"14",29.75,77.259424323396,1,695.334818910564,1,0 +"14",29.75,77.259424323396,1,546.990424094061,2,0 +"15",23.5,33.8125401953475,1,0,0,0 +"15",23.5,33.8125401953475,1,304.312861758127,1,0 +"15",23.5,33.8125401953475,1,260.858966314105,2,0 +"15",24.5,90.5490415103391,1,0,0,0 +"15",24.5,90.5490415103391,1,814.941373593052,1,0 +"15",24.5,90.5490415103391,1,322.33515128386,2,0 +"15",29.75,62.5165876288004,1,0,0,0 +"15",29.75,62.5165876288004,1,562.649288659203,1,0 +"15",29.75,62.5165876288004,1,440.702867644443,2,0 +"16",19.7,30.5167178573736,1,0,0,0 +"16",19.7,30.5167178573736,1,274.650460716362,1,0 +"16",19.7,30.5167178573736,1,235.33583724646,2,0 +"16",21,75.447452002584,1,0,0,0 +"16",21,75.447452002584,1,679.027068023256,1,0 +"16",21,75.447452002584,1,299.681155882777,2,0 +"16",23.9,59.5379333719963,1,0,0,0 +"16",23.9,59.5379333719963,1,535.841400347967,1,0 +"16",23.9,59.5379333719963,1,375.635234421265,2,0 +"16",25.25,101.499769742421,1,0,0,0 +"16",25.25,101.499769742421,1,913.497927681793,1,0 +"16",25.25,101.499769742421,1,453.213952527019,2,0 +"16",26,94.6370950562267,1,0,0,0 +"16",26,94.6370950562267,1,851.73385550604,1,0 +"16",26,94.6370950562267,1,489.273979491205,2,0 +"16",30.25,75.254483983167,1,0,0,0 +"16",30.25,75.254483983167,1,677.290355848503,1,0 +"16",30.25,75.254483983167,1,540.084574943178,2,0 +"17",24.5,27.592759848218,1,0,0,0 +"17",24.5,27.592759848218,1,248.334838633962,1,0 +"17",24.5,27.592759848218,1,212.882319477361,2,0 +"17",25.75,74.3053134851355,1,0,0,0 +"17",25.75,74.3053134851355,1,668.74782136622,1,0 +"17",25.75,74.3053134851355,1,266.233707163278,2,0 +"17",31.3,50.8254705507865,1,0,0,0 +"17",31.3,50.8254705507865,1,457.429234957079,1,0 +"17",31.3,50.8254705507865,1,362.178744018387,2,0 +"18",24.75,27.4486025627351,1,0,0,0 +"18",24.75,27.4486025627351,1,247.037423064616,1,0 +"18",24.75,27.4486025627351,1,211.771681018646,2,0 +"18",26.75,70.1603698548436,1,0,0,0 +"18",26.75,70.1603698548436,1,631.443328693593,1,0 +"18",26.75,70.1603698548436,1,287.049442428173,2,0 +"18",28.75,58.8507762553355,1,0,0,0 +"18",28.75,58.8507762553355,1,529.656986298019,1,0 +"18",28.75,58.8507762553355,1,344.126714076995,2,0 +"18",30.3,101.748972993084,1,0,0,0 +"18",30.3,101.748972993084,1,915.740756937759,1,0 +"18",30.3,101.748972993084,1,414.075803113218,2,0 +"18",31,94.6673266606322,1,0,0,0 +"18",31,94.6673266606322,1,852.00593994569,1,0 +"18",31,94.6673266606322,1,453.78550900746,2,0 +"18",33.3,80.2574136443223,1,0,0,0 +"18",33.3,80.2574136443223,1,722.316722798901,1,0 +"18",33.3,80.2574136443223,1,513.76582594953,2,0 +"19",24,27.8833820634924,1,0,0,0 +"19",24,27.8833820634924,1,250.950438571431,1,0 +"19",24,27.8833820634924,1,215.120836882717,2,0 +"19",25.3,72.4892811079128,1,0,0,0 +"19",25.3,72.4892811079128,1,652.403529971215,1,0 +"19",25.3,72.4892811079128,1,282.773854880199,2,0 +"19",27.8,58.3727112439312,1,0,0,0 +"19",27.8,58.3727112439312,1,525.354401195381,1,0 +"19",27.8,58.3727112439312,1,353.430305760927,2,0 +"2",23.8,39.2006999907033,1,0,0,0 +"2",23.8,39.2006999907033,1,352.806299916329,1,0 +"2",23.8,39.2006999907033,1,302.431791861967,2,0 +"2",24.7,106.745059486943,1,0,0,0 +"2",24.7,106.745059486943,1,960.70553538249,1,0 +"2",24.7,106.745059486943,1,365.786029144146,2,0 +"2",28.75,76.8589891774555,1,0,0,0 +"2",28.75,76.8589891774555,1,691.730902597099,1,0 +"2",28.75,76.8589891774555,1,509.205708001681,2,0 +"20",25.8,45.6473975490521,1,0,0,0 +"20",25.8,45.6473975490521,1,410.826577941469,1,0 +"20",25.8,45.6473975490521,1,352.18762325485,2,0 +"20",31.4,95.0318364345088,1,0,0,0 +"20",31.4,95.0318364345088,1,855.286527910579,1,0 +"20",31.4,95.0318364345088,1,581.673138374367,2,0 +"21",20.75,20.8953646657747,1,0,0,0 +"21",20.75,20.8953646657747,1,188.058281991972,1,0 +"21",20.75,20.8953646657747,1,161.167729218474,2,0 +"21",21.25,57.6128998165303,1,0,0,0 +"21",21.25,57.6128998165303,1,518.516098348773,1,0 +"21",21.25,57.6128998165303,1,173.785371585654,2,0 +"21",22.25,50.9343510604735,1,0,0,0 +"21",22.25,50.9343510604735,1,458.409159544262,1,0 +"21",22.25,50.9343510604735,1,214.968229613216,2,0 +"21",22.25,50.9343510604735,1,0,0,0 +"21",22.25,50.9343510604735,1,458.409159544262,1,0 +"21",22.25,50.9343510604735,1,214.968229613216,2,0 +"21",23.5,111.152621744224,1,0,0,0 +"21",23.5,111.152621744224,1,1000.37359569802,1,0 +"21",23.5,111.152621744224,1,330.848174247311,2,0 +"21",23.5,111.152621744224,1,0,0,0 +"21",23.5,111.152621744224,1,1000.37359569802,1,0 +"21",23.5,111.152621744224,1,330.848174247311,2,0 +"21",24,104.067924774948,1,0,0,0 +"21",24,104.067924774948,1,936.611322974528,1,0 +"21",24,104.067924774948,1,375.797933142136,2,0 +"21",25.25,124.60886285143,1,0,0,0 +"21",25.25,124.60886285143,1,1121.47976566287,1,0 +"21",25.25,124.60886285143,1,489.005580575595,2,0 +"21",25.9,116.230723852285,1,0,0,0 +"21",25.9,116.230723852285,1,1046.07651467057,1,0 +"21",25.9,116.230723852285,1,537.050271461683,2,0 +"21",27.3,139.664654668596,1,0,0,0 +"21",27.3,139.664654668596,1,1256.98189201736,1,0 +"21",27.3,139.664654668596,1,617.994357822965,2,0 +"21",27.8,133.038612748303,1,0,0,0 +"21",27.8,133.038612748303,1,1197.34751473473,1,0 +"21",27.8,133.038612748303,1,653.78409270999,2,0 +"21",29,121.04196623897,1,0,0,0 +"21",29,121.04196623897,1,1089.37769615073,1,0 +"21",29,121.04196623897,1,708.562054027174,2,0 +"21",29,121.04196623897,1,0,0,0 +"21",29,121.04196623897,1,1089.37769615073,1,0 +"21",29,121.04196623897,1,708.562054027174,2,0 +"21",29.75,115.54856908762,1,0,0,0 +"21",29.75,115.54856908762,1,1039.93712178858,1,0 +"21",29.75,115.54856908762,1,726.976981347421,2,0 +"21",32,104.380251864236,1,0,0,0 +"21",32,104.380251864236,1,939.422266778121,1,0 +"21",32,104.380251864236,1,741.295833466881,2,0 +"22",24.5,38.6298637875052,1,0,0,0 +"22",24.5,38.6298637875052,1,347.668774087547,1,0 +"22",24.5,38.6298637875052,1,298.035247268305,2,0 +"22",24.8,83.6114584539572,1,0,0,0 +"22",24.8,83.6114584539572,1,752.503126085614,1,0 +"22",24.8,83.6114584539572,1,306.758485422218,2,0 +"22",25.5,140.32141663658,1,0,0,0 +"22",25.5,140.32141663658,1,1262.89274972922,1,0 +"22",25.5,140.32141663658,1,394.729137706478,2,0 +"22",26,131.119427317789,1,0,0,0 +"22",26,131.119427317789,1,1180.0748458601,1,0 +"22",26,131.119427317789,1,453.821144347717,2,0 +"22",31,90.7685129347638,1,0,0,0 +"22",31,90.7685129347638,1,816.916616412875,1,0 +"22",31,90.7685129347638,1,631.766352521024,2,0 +"22",31,90.7685129347638,1,0,0,0 +"22",31,90.7685129347638,1,816.916616412875,1,0 +"22",31,90.7685129347638,1,631.766352521024,2,0 +"22",47.5,61.2004318689655,1,0,0,0 +"22",47.5,61.2004318689655,1,550.803886820689,1,0 +"22",47.5,61.2004318689655,1,472.139283379452,2,0 +"22",49.25,91.023713105017,1,0,0,0 +"22",49.25,91.023713105017,1,819.213417945153,1,0 +"22",49.25,91.023713105017,1,504.616959431009,2,0 +"22",55,70.7795826452237,1,0,0,0 +"22",55,70.7795826452237,1,637.016243807014,1,0 +"22",55,70.7795826452237,1,528.370321738057,2,0 +"23",21.6,20.5257782946295,1,0,0,0 +"23",21.6,20.5257782946295,1,184.732004651666,1,0 +"23",21.6,20.5257782946295,1,158.332783416931,2,0 +"23",22.3,55.7083131905019,1,0,0,0 +"23",22.3,55.7083131905019,1,501.374818714517,1,0 +"23",22.3,55.7083131905019,1,180.876977641438,2,0 +"23",23.75,47.3707473163243,1,0,0,0 +"23",23.75,47.3707473163243,1,426.336725846919,1,0 +"23",23.75,47.3707473163243,1,229.942740437881,2,0 +"23",27.3,37.9800196173675,1,0,0,0 +"23",27.3,37.9800196173675,1,341.820176556307,1,0 +"23",27.3,37.9800196173675,1,262.439192964923,2,0 +"24",24.25,38.8327584466729,1,0,0,0 +"24",24.25,38.8327584466729,1,349.494826020056,1,0 +"24",24.25,38.8327584466729,1,299.598177197527,2,0 +"24",26.25,89.732346641963,1,0,0,0 +"24",26.25,89.732346641963,1,807.591119777667,1,0 +"24",26.25,89.732346641963,1,301.626474081559,2,0 +"24",31.8,72.2249439711969,1,0,0,0 +"24",31.8,72.2249439711969,1,650.024495740772,1,0 +"24",31.8,72.2249439711969,1,500.987674000814,2,0 +"26",23,28.4740094946886,1,0,0,0 +"26",23,28.4740094946886,1,256.266085452197,1,0 +"26",23,28.4740094946886,1,219.667365096445,2,0 +"26",23.75,80.2604203358494,1,0,0,0 +"26",23.75,80.2604203358494,1,722.343783022645,1,0 +"26",23.75,80.2604203358494,1,240.559300505424,2,0 +"26",24.2,75.6315368539436,1,0,0,0 +"26",24.2,75.6315368539436,1,680.683831685493,1,0 +"26",24.2,75.6315368539436,1,269.952970905393,2,0 +"26",24.25,75.1634497128808,1,0,0,0 +"26",24.25,75.1634497128808,1,676.471047415927,1,0 +"26",24.25,75.1634497128808,1,272.846311652749,2,0 +"26",25.4,140.95539202032,1,0,0,0 +"26",25.4,140.95539202032,1,1268.59852818288,1,0 +"26",25.4,140.95539202032,1,426.797078033385,2,0 +"26",25.75,161.608981257821,1,0,0,0 +"26",25.75,161.608981257821,1,1454.48083132039,1,0 +"26",25.75,161.608981257821,1,472.85443307454,2,0 +"26",26.5,171.186407359689,1,0,0,0 +"26",26.5,171.186407359689,1,1540.6776662372,1,0 +"26",26.5,171.186407359689,1,590.586186159816,2,0 +"26",27,161.1871482765,1,0,0,0 +"26",27,161.1871482765,1,1450.6843344885,1,0 +"26",27,161.1871482765,1,651.523591407901,2,0 +"26",28.3,261.420493379511,1,0,0,0 +"26",28.3,261.420493379511,1,2352.7844404156,1,0 +"26",28.3,261.420493379511,1,855.777736742064,2,0 +"26",30,217.566747220341,1,0,0,0 +"26",30,217.566747220341,1,1958.10072498307,1,0 +"26",30,217.566747220341,1,1109.28032750093,2,0 +"26",30.7,205.804299201231,1,0,0,0 +"26",30.7,205.804299201231,1,1852.23869281108,1,0 +"26",30.7,205.804299201231,1,1163.33475191969,2,0 +"26",30.75,205.062335437708,1,0,0,0 +"26",30.75,205.062335437708,1,1845.56101893938,1,0 +"26",30.75,205.062335437708,1,1166.41735997712,2,0 +"26",32,189.762467875735,1,0,0,0 +"26",32,189.762467875735,1,1707.86221088162,1,0 +"26",32,189.762467875735,1,1217.95574330159,2,0 +"26",32.4,185.915662500833,1,0,0,0 +"26",32.4,185.915662500833,1,1673.2409625075,1,0 +"26",32.4,185.915662500833,1,1226.28434476592,2,0 +"26",35,168.057726057862,1,0,0,0 +"26",35,168.057726057862,1,1512.51953452076,1,0 +"26",35,168.057726057862,1,1226.69109633002,2,0 +"27",24.5,38.6298637875052,1,0,0,0 +"27",24.5,38.6298637875052,1,347.668774087547,1,0 +"27",24.5,38.6298637875052,1,298.035247268305,2,0 +"27",25.5,104.839644121655,1,0,0,0 +"27",25.5,104.839644121655,1,943.556797094895,1,0 +"27",25.5,104.839644121655,1,369.88601607276,2,0 +"27",30,74.4441914618989,1,0,0,0 +"27",30,74.4441914618989,1,669.99772315709,1,0 +"27",30,74.4441914618989,1,507.834542888599,2,0 +"28",25,38.2272907290519,1,0,0,0 +"28",25,38.2272907290519,1,344.045616561467,1,0 +"28",25,38.2272907290519,1,294.933466035921,2,0 +"28",26.25,103.635370811984,1,0,0,0 +"28",26.25,103.635370811984,1,932.71833730786,1,0 +"28",26.25,103.635370811984,1,369.704679569004,2,0 +"28",30.9,73.3146949316007,1,0,0,0 +"28",30.9,73.3146949316007,1,659.832254384406,1,0 +"28",30.9,73.3146949316007,1,504.47313866455,2,0 +"29",24.25,27.7376846047664,1,0,0,0 +"29",24.25,27.7376846047664,1,249.639161442897,1,0 +"29",24.25,27.7376846047664,1,213.998697998233,2,0 +"29",25.5,73.1798809049263,1,0,0,0 +"29",25.5,73.1798809049263,1,658.618928144336,1,0 +"29",25.5,73.1798809049263,1,276.264450362672,2,0 +"29",25.8,70.6893916524197,1,0,0,0 +"29",25.8,70.6893916524197,1,636.204524871777,1,0 +"29",25.8,70.6893916524197,1,291.12809666494,2,0 +"29",27.6,103.305968112974,1,0,0,0 +"29",27.6,103.305968112974,1,929.753713016767,1,0 +"29",27.6,103.305968112974,1,433.669876931814,2,0 +"29",31.5,78.5774173635571,1,0,0,0 +"29",31.5,78.5774173635571,1,707.196756272014,1,0 +"29",31.5,78.5774173635571,1,535.432578347529,2,0 +"29",32.6,75.2416614619601,1,0,0,0 +"29",32.6,75.2416614619601,1,677.174953157641,1,0 +"29",32.6,75.2416614619601,1,535.869755068107,2,0 +"3",22,40.7084547881821,1,0,0,0 +"3",22,40.7084547881821,1,366.376093093639,1,0 +"3",22,40.7084547881821,1,314.030317180539,2,0 +"3",23.6,102.315984159141,1,0,0,0 +"3",23.6,102.315984159141,1,920.843857432272,1,0 +"3",23.6,102.315984159141,1,410.308174048819,2,0 +"3",26.1,82.8123329890685,1,0,0,0 +"3",26.1,82.8123329890685,1,745.310996901617,1,0 +"3",26.1,82.8123329890685,1,506.026283918139,2,0 +"3",27,147.068593607483,1,0,0,0 +"3",27,147.068593607483,1,1323.61734246735,1,0 +"3",27,147.068593607483,1,585.779935666064,2,0 +"3",29.2,121.180625313537,1,0,0,0 +"3",29.2,121.180625313537,1,1090.62562782183,1,0 +"3",29.2,121.180625313537,1,716.725864810124,2,0 +"3",32.7,103.013225239889,1,0,0,0 +"3",32.7,103.013225239889,1,927.119027159,1,0 +"3",32.7,103.013225239889,1,744.500504173207,2,0 +"30",24.2,27.7667621102124,1,0,0,0 +"30",24.2,27.7667621102124,1,249.900858991911,1,0 +"30",24.2,27.7667621102124,1,214.222664312871,2,0 +"30",24.2,27.7667621102124,1,0,0,0 +"30",24.2,27.7667621102124,1,249.900858991911,1,0 +"30",24.2,27.7667621102124,1,214.222664312871,2,0 +"30",25.25,122.453110051764,1,0,0,0 +"30",25.25,122.453110051764,1,1102.07799046588,1,0 +"30",25.25,122.453110051764,1,324.145459098153,2,0 +"30",25.4,119.809694792101,1,0,0,0 +"30",25.4,119.809694792101,1,1078.28725312891,1,0 +"30",25.4,119.809694792101,1,341.577456672036,2,0 +"30",26.8,101.115178595864,1,0,0,0 +"30",26.8,101.115178595864,1,910.036607362774,1,0 +"30",26.8,101.115178595864,1,456.092643169297,2,0 +"30",30.75,110.365033786295,1,0,0,0 +"30",30.75,110.365033786295,1,993.285304076653,1,0 +"30",30.75,110.365033786295,1,698.629852636967,2,0 +"30",31,108.88100139113,1,0,0,0 +"30",31,108.88100139113,1,979.929012520166,1,0 +"30",31,108.88100139113,1,702.39488227808,2,0 +"30",31.2,107.764860939768,1,0,0,0 +"30",31.2,107.764860939768,1,969.883748457912,1,0 +"30",31.2,107.764860939768,1,704.857892426452,2,0 +"30",33.3,98.8045329168785,1,0,0,0 +"30",33.3,98.8045329168785,1,889.240796251906,1,0 +"30",33.3,98.8045329168785,1,709.846644660416,2,0 +"31",23.5,28.1771168294562,1,0,0,0 +"31",23.5,28.1771168294562,1,253.594051465106,1,0 +"31",23.5,28.1771168294562,1,217.382471928421,2,0 +"31",25,76.0743058574958,1,0,0,0 +"31",25,76.0743058574958,1,684.668752717462,1,0 +"31",25,76.0743058574958,1,259.949146047345,2,0 +"31",25.4,104.735104784676,1,0,0,0 +"31",25.4,104.735104784676,1,942.615943062086,1,0 +"31",25.4,104.735104784676,1,289.89917293189,2,0 +"31",26.1,115.06830536299,1,0,0,0 +"31",26.1,115.06830536299,1,1035.61474826691,1,0 +"31",26.1,115.06830536299,1,368.048535770384,2,0 +"31",29.4,85.0467293439894,1,0,0,0 +"31",29.4,85.0467293439894,1,765.420564095905,1,0 +"31",29.4,85.0467293439894,1,525.87875682696,2,0 +"31",30,82.2624437967993,1,0,0,0 +"31",30,82.2624437967993,1,740.361994171194,1,0 +"31",30,82.2624437967993,1,533.378099223054,2,0 +"32",24,47.401749507937,1,0,0,0 +"32",24,47.401749507937,1,426.615745571433,1,0 +"32",24,47.401749507937,1,365.705422700619,2,0 +"32",26.25,111.663043166486,1,0,0,0 +"32",26.25,111.663043166486,1,1004.96738849838,1,0 +"32",26.25,111.663043166486,1,545.890948679455,2,0 +"32",26.5,109.206021051161,1,0,0,0 +"32",26.5,109.206021051161,1,982.854189460452,1,0 +"32",26.5,109.206021051161,1,558.342655628495,2,0 +"32",27.3,102.589121215106,1,0,0,0 +"32",27.3,102.589121215106,1,923.302090935952,1,0 +"32",27.3,102.589121215106,1,588.283863740239,2,0 +"32",28.75,169.22465358868,1,0,0,0 +"32",28.75,169.22465358868,1,1523.02188229812,1,0 +"32",28.75,169.22465358868,1,753.574126201054,2,0 +"32",30.5,146.314020290635,1,0,0,0 +"32",30.5,146.314020290635,1,1316.82618261572,1,0 +"32",30.5,146.314020290635,1,863.815920943521,2,0 +"32",34.25,123.298023350403,1,0,0,0 +"32",34.25,123.298023350403,1,1109.68221015363,1,0 +"32",34.25,123.298023350403,1,896.296992076392,2,0 +"33",24.6,38.5490071755298,1,0,0,0 +"33",24.6,38.5490071755298,1,346.941064579768,1,0 +"33",24.6,38.5490071755298,1,297.412327034966,2,0 +"33",26,100.932023672922,1,0,0,0 +"33",26,100.932023672922,1,908.388213056299,1,0 +"33",26,100.932023672922,1,391.621023844417,2,0 +"33",31,71.9415492606667,1,0,0,0 +"33",31,71.9415492606667,1,647.473943346,1,0 +"33",31,71.9415492606667,1,507.489417265583,2,0 +"34",24,41.8250730952385,1,0,0,0 +"34",24,41.8250730952385,1,376.425657857147,1,0 +"34",24,41.8250730952385,1,322.681255324075,2,0 +"34",25.7,112.158736358382,1,0,0,0 +"34",25.7,112.158736358382,1,1009.42862722544,1,0 +"34",25.7,112.158736358382,1,395.000292838644,2,0 +"34",31.7,75.1888322078639,1,0,0,0 +"34",31.7,75.1888322078639,1,676.699489870775,1,0 +"34",31.7,75.1888322078639,1,542.113500547604,2,0 +"36",24.3,27.7086380068948,1,0,0,0 +"36",24.3,27.7086380068948,1,249.377742062053,1,0 +"36",24.3,27.7086380068948,1,213.774962101268,2,0 +"37",20.2,30.1975099010882,1,0,0,0 +"37",20.2,30.1975099010882,1,271.777589109794,1,0 +"37",20.2,30.1975099010882,1,232.896266927543,2,0 +"37",20.9,80.9488282110897,1,0,0,0 +"37",20.9,80.9488282110897,1,728.539453899808,1,0 +"37",20.9,80.9488282110897,1,261.365992804701,2,0 +"37",21.3,76.9335227219167,1,0,0,0 +"37",21.3,76.9335227219167,1,692.40170449725,1,0 +"37",21.3,76.9335227219167,1,286.458929489453,2,0 +"37",22.2,118.549029789557,1,0,0,0 +"37",22.2,118.549029789557,1,1066.94126810601,1,0 +"37",22.2,118.549029789557,1,377.076856000894,2,0 +"37",24.5,93.6214354136422,1,0,0,0 +"37",24.5,93.6214354136422,1,842.59291872278,1,0 +"37",24.5,93.6214354136422,1,517.373717271595,2,0 +"37",25.3,88.6028756154714,1,0,0,0 +"37",25.3,88.6028756154714,1,797.425880539242,1,0 +"37",25.3,88.6028756154714,1,537.058602651748,2,0 +"38",24,27.8833820634924,1,0,0,0 +"38",24,27.8833820634924,1,250.950438571431,1,0 +"38",24,27.8833820634924,1,215.120836882717,2,0 +"38",24.2,49.5248635511217,1,0,0,0 +"38",24.2,49.5248635511217,1,445.723771960095,1,0 +"38",24.2,49.5248635511217,1,217.63287461329,2,0 +"38",24.3,71.0047898552247,1,0,0,0 +"38",24.3,71.0047898552247,1,639.043108697022,1,0 +"38",24.3,71.0047898552247,1,222.202958917585,2,0 +"38",24.5,112.660763977238,1,0,0,0 +"38",24.5,112.660763977238,1,1013.94687579514,1,0 +"38",24.5,112.660763977238,1,240.861022741257,2,0 +"38",25,189.484936372648,1,0,0,0 +"38",25,189.484936372648,1,1705.36442735383,1,0 +"38",25,189.484936372648,1,324.062885664534,2,0 +"38",25.1,208.283708210279,1,0,0,0 +"38",25.1,208.283708210279,1,1874.55337389251,1,0 +"38",25.1,208.283708210279,1,347.911986531194,2,0 +"38",25.7,208.909177063798,1,0,0,0 +"38",25.7,208.909177063798,1,1880.18259357419,1,0 +"38",25.7,208.909177063798,1,496.599225084128,2,0 +"38",25.7,208.909177063798,1,0,0,0 +"38",25.7,208.909177063798,1,1880.18259357419,1,0 +"38",25.7,208.909177063798,1,496.599225084128,2,0 +"38",30,138.31806895544,1,0,0,0 +"38",30,138.31806895544,1,1244.86262059896,1,0 +"38",30,138.31806895544,1,883.358612185906,2,0 +"38",30.3,136.15616819969,1,0,0,0 +"38",30.3,136.15616819969,1,1225.40551379721,1,0 +"38",30.3,136.15616819969,1,888.407370486747,2,0 +"38",30.5,134.808726878048,1,0,0,0 +"38",30.5,134.808726878048,1,1213.27854190244,1,0 +"38",30.5,134.808726878048,1,891.050985466827,2,0 +"38",31.2,130.596188162139,1,0,0,0 +"38",31.2,130.596188162139,1,1175.36569345925,1,0 +"38",31.2,130.596188162139,1,896.466562865013,2,0 +"39",24.75,38.4280435878291,1,0,0,0 +"39",24.75,38.4280435878291,1,345.852392290462,1,0 +"39",24.75,38.4280435878291,1,296.480353426105,2,0 +"39",26.2,101.901194386174,1,0,0,0 +"39",26.2,101.901194386174,1,917.110749475562,1,0 +"39",26.2,101.901194386174,1,382.525504782713,2,0 +"39",30.5,74.0856270816154,1,0,0,0 +"39",30.5,74.0856270816154,1,666.770643734538,1,0 +"39",30.5,74.0856270816154,1,505.068385634433,2,0 +"4",14.5,47.7219692293984,1,0,0,0 +"4",14.5,47.7219692293984,1,429.497723064585,1,0 +"4",14.5,47.7219692293984,1,366.681031794116,2,0 +"4",18.1,92.2448514048423,1,0,0,0 +"4",18.1,92.2448514048423,1,830.20366264358,1,0 +"4",18.1,92.2448514048423,1,544.009851823586,2,0 +"4",19.8,84.0055105553079,1,0,0,0 +"4",19.8,84.0055105553079,1,756.049594997771,1,0 +"4",19.8,84.0055105553079,1,565.923103252142,2,0 +"40",25.25,38.0275982798858,1,0,0,0 +"40",25.25,38.0275982798858,1,342.248384518972,1,0 +"40",25.25,38.0275982798858,1,293.394554391563,2,0 +"40",26.4,104.133168020086,1,0,0,0 +"40",26.4,104.133168020086,1,937.198512180772,1,0 +"40",26.4,104.133168020086,1,364.439801029568,2,0 +"40",26.75,99.8019618579081,1,0,0,0 +"40",26.75,99.8019618579081,1,898.217656721173,1,0 +"40",26.75,99.8019618579081,1,390.935320463854,2,0 +"40",28.1,150.865877781574,1,0,0,0 +"40",28.1,150.865877781574,1,1357.79290003417,1,0 +"40",28.1,150.865877781574,1,561.642594117256,2,0 +"40",29.9,126.592116930324,1,0,0,0 +"40",29.9,126.592116930324,1,1139.32905237291,1,0 +"40",29.9,126.592116930324,1,693.504138150488,2,0 +"40",32.75,107.856335483063,1,0,0,0 +"40",32.75,107.856335483063,1,970.707019347569,1,0 +"40",32.75,107.856335483063,1,746.432896139318,2,0 +"41",23,19.931806646282,1,0,0,0 +"41",23,19.931806646282,1,179.386259816538,1,0 +"41",23,19.931806646282,1,153.767155567512,2,0 +"41",24.7,52.7395033283625,1,0,0,0 +"41",24.7,52.7395033283625,1,474.655529955262,1,0 +"41",24.7,52.7395033283625,1,187.406559971658,2,0 +"41",29,37.8447912570935,1,0,0,0 +"41",29,37.8447912570935,1,340.603121313841,1,0 +"41",29,37.8447912570935,1,255.810766747285,2,0 +"42",24.3,27.7086380068948,1,0,0,0 +"42",24.3,27.7086380068948,1,249.377742062053,1,0 +"42",24.3,27.7086380068948,1,213.774962101268,2,0 +"42",25.25,75.4750613693008,1,0,0,0 +"42",25.25,75.4750613693008,1,679.275552323707,1,0 +"42",25.25,75.4750613693008,1,262.109935859119,2,0 +"42",27,63.0025230673998,1,0,0,0 +"42",27,63.0025230673998,1,567.022707606598,1,0 +"42",27,63.0025230673998,1,332.346794618639,2,0 +"42",28.3,104.497950764628,1,0,0,0 +"42",28.3,104.497950764628,1,940.48155688165,1,0 +"42",28.3,104.497950764628,1,416.411469314046,2,0 +"42",32,79.1932569266208,1,0,0,0 +"42",32,79.1932569266208,1,712.739312339587,1,0 +"42",32,79.1932569266208,1,528.378796284106,2,0 +"43",24.4,27.6506373647904,1,0,0,0 +"43",24.4,27.6506373647904,1,248.855736283113,1,0 +"43",24.4,27.6506373647904,1,213.328180920807,2,0 +"43",25.7,73.0669117236177,1,0,0,0 +"43",25.7,73.0669117236177,1,657.60220551256,1,0 +"43",25.7,73.0669117236177,1,275.393681726939,2,0 +"43",31,51.1094999420686,1,0,0,0 +"43",31,51.1094999420686,1,459.985499478618,1,0 +"43",31,51.1094999420686,1,363.086708690916,2,0 +"44",24.5,38.6298637875052,1,0,0,0 +"44",24.5,38.6298637875052,1,347.668774087547,1,0 +"44",24.5,38.6298637875052,1,298.035247268305,2,0 +"44",25.6,82.9734480993181,1,0,0,0 +"44",25.6,82.9734480993181,1,746.761032893863,1,0 +"44",25.6,82.9734480993181,1,301.841565943278,2,0 +"44",26.5,101.667684601807,1,0,0,0 +"44",26.5,101.667684601807,1,915.009161416259,1,0 +"44",26.5,101.667684601807,1,380.725141365318,2,0 +"44",29.2,135.357073848679,1,0,0,0 +"44",29.2,135.357073848679,1,1218.21366463811,1,0 +"44",29.2,135.357073848679,1,648.752661282045,2,0 +"44",31.8,112.969089619673,1,0,0,0 +"44",31.8,112.969089619673,1,1016.72180657706,1,0 +"44",31.8,112.969089619673,1,738.620767492659,2,0 +"44",32.75,108.157476965413,1,0,0,0 +"44",32.75,108.157476965413,1,973.417292688716,1,0 +"44",32.75,108.157476965413,1,745.194409986674,2,0 +"45",24.5,38.6298637875052,1,0,0,0 +"45",24.5,38.6298637875052,1,347.668774087547,1,0 +"45",24.5,38.6298637875052,1,298.035247268305,2,0 +"45",25.8,104.602659093384,1,0,0,0 +"45",25.8,104.602659093384,1,941.423931840459,1,0 +"45",25.8,104.602659093384,1,368.059295439408,2,0 +"45",30.7,72.9974346968619,1,0,0,0 +"45",30.7,72.9974346968619,1,656.976912271757,1,0 +"45",30.7,72.9974346968619,1,506.947384311779,2,0 +"46",23.6,47.8008018589561,1,0,0,0 +"46",23.6,47.8008018589561,1,430.207216730605,1,0 +"46",23.6,47.8008018589561,1,368.778090405518,2,0 +"46",24,93.3991881578212,1,0,0,0 +"46",24,93.3991881578212,1,840.59269342039,1,0 +"46",24,93.3991881578212,1,374.698520041486,2,0 +"46",24.25,182.852676624967,1,0,0,0 +"46",24.25,182.852676624967,1,1645.6740896247,1,0 +"46",24.25,182.852676624967,1,407.497641387995,2,0 +"46",25.25,279.851579772235,1,0,0,0 +"46",25.25,279.851579772235,1,2518.66421795011,1,0 +"46",25.25,279.851579772235,1,713.824048065518,2,0 +"46",25.25,279.851579772235,1,0,0,0 +"46",25.25,279.851579772235,1,2518.66421795011,1,0 +"46",25.25,279.851579772235,1,713.824048065518,2,0 +"46",25.25,279.851579772235,1,0,0,0 +"46",25.25,279.851579772235,1,2518.66421795011,1,0 +"46",25.25,279.851579772235,1,713.824048065518,2,0 +"46",26.2,327.365298229438,1,0,0,0 +"46",26.2,327.365298229438,1,2946.28768406494,1,0 +"46",26.2,327.365298229438,1,957.582556595319,2,0 +"46",26.6,382.693402461191,1,0,0,0 +"46",26.6,382.693402461191,1,3444.24062215072,1,0 +"46",26.6,382.693402461191,1,1089.88564173509,2,0 +"46",27.5,364.932481725091,1,0,0,0 +"46",27.5,364.932481725091,1,3284.39233552582,1,0 +"46",27.5,364.932481725091,1,1383.60994784003,2,0 +"46",27.75,354.525227136158,1,0,0,0 +"46",27.75,354.525227136158,1,3190.72704422542,1,0 +"46",27.75,354.525227136158,1,1445.80587491599,2,0 +"46",29.8,296.219454522613,1,0,0,0 +"46",29.8,296.219454522613,1,2665.97509070352,1,0 +"46",29.8,296.219454522613,1,1739.50949999075,2,0 +"46",30.25,287.8525802075,1,0,0,0 +"46",30.25,287.8525802075,1,2590.6732218675,1,0 +"46",30.25,287.8525802075,1,1768.83062777834,2,0 +"46",31.5,269.538900609733,1,0,0,0 +"46",31.5,269.538900609733,1,2425.85010548759,1,0 +"46",31.5,269.538900609733,1,1811.9516105181,2,0 +"46",32,263.719233623244,1,0,0,0 +"46",32,263.719233623244,1,2373.4731026092,1,0 +"46",32,263.719233623244,1,1817.67866679575,2,0 +"46",32.5,258.529846117696,1,0,0,0 +"46",32.5,258.529846117696,1,2326.76861505927,1,0 +"46",32.5,258.529846117696,1,1818.694654305,2,0 +"47",25.5,37.8289594804313,1,0,0,0 +"47",25.5,37.8289594804313,1,340.460635323882,1,0 +"47",25.5,37.8289594804313,1,291.863586843229,2,0 +"47",27.1,98.9967561304258,1,0,0,0 +"47",27.1,98.9967561304258,1,890.970805173832,1,0 +"47",27.1,98.9967561304258,1,392.587457835353,2,0 +"47",32.5,69.9654955878374,1,0,0,0 +"47",32.5,69.9654955878374,1,629.689460290536,1,0 +"47",32.5,69.9654955878374,1,501.243035374257,2,0 +"48",26.6,36.9673017651368,1,0,0,0 +"48",26.6,36.9673017651368,1,332.705715886231,1,0 +"48",26.6,36.9673017651368,1,285.220867658835,2,0 +"48",28.3,97.0474752820713,1,0,0,0 +"48",28.3,97.0474752820713,1,873.427277538641,1,0 +"48",28.3,97.0474752820713,1,392.791755171299,2,0 +"48",28.4,111.36199444499,1,0,0,0 +"48",28.4,111.36199444499,1,1002.25795000491,1,0 +"48",28.4,111.36199444499,1,400.31307670494,2,0 +"48",30,142.41993826697,1,0,0,0 +"48",30,142.41993826697,1,1281.77944440273,1,0 +"48",30,142.41993826697,1,596.951322282702,2,0 +"48",32.8,114.276286560584,1,0,0,0 +"48",32.8,114.276286560584,1,1028.48657904525,1,0 +"48",32.8,114.276286560584,1,726.53972174863,2,0 +"48",35.2,102.955467648506,1,0,0,0 +"48",35.2,102.955467648506,1,926.599208836554,1,0 +"48",35.2,102.955467648506,1,737.637170204453,2,0 +"49",22.9,28.5337711990384,1,0,0,0 +"49",22.9,28.5337711990384,1,256.803940791345,1,0 +"49",22.9,28.5337711990384,1,220.127145544773,2,0 +"49",24.1,75.6897847581815,1,0,0,0 +"49",24.1,75.6897847581815,1,681.208062823633,1,0 +"49",24.1,75.6897847581815,1,270.401595417849,2,0 +"49",29.6,51.8259002912864,1,0,0,0 +"49",29.6,51.8259002912864,1,466.433102621578,1,0 +"49",29.6,51.8259002912864,1,368.613410319149,2,0 +"5",24.25,47.1540638281028,1,0,0,0 +"5",24.25,47.1540638281028,1,424.386574452925,1,0 +"5",24.25,47.1540638281028,1,363.797786596997,2,0 +"5",29.75,99.1570490764455,1,0,0,0 +"5",29.75,99.1570490764455,1,892.41344168801,1,0 +"5",29.75,99.1570490764455,1,578.155643192394,2,0 +"5",32.75,85.4132062721791,1,0,0,0 +"5",32.75,85.4132062721791,1,768.718856449612,1,0 +"5",32.75,85.4132062721791,1,605.883496779736,2,0 +"50",24.4,27.6506373647904,1,0,0,0 +"50",24.4,27.6506373647904,1,248.855736283113,1,0 +"50",24.4,27.6506373647904,1,213.328180920807,2,0 +"50",25.4,74.9421240210109,1,0,0,0 +"50",25.4,74.9421240210109,1,674.479116189098,1,0 +"50",25.4,74.9421240210109,1,264.641037044208,2,0 +"50",26.4,67.036385860659,1,0,0,0 +"50",26.4,67.036385860659,1,603.327472745931,1,0 +"50",26.4,67.036385860659,1,311.029579904485,2,0 +"51",24,27.8833820634924,1,0,0,0 +"51",24,27.8833820634924,1,250.950438571431,1,0 +"51",24,27.8833820634924,1,215.120836882717,2,0 +"51",29.1,72.9230823038041,1,0,0,0 +"51",29.1,72.9230823038041,1,656.307740734237,1,0 +"51",29.1,72.9230823038041,1,249.071538999431,2,0 +"51",32.5,54.2703706846087,1,0,0,0 +"51",32.5,54.2703706846087,1,488.433336161478,1,0 +"51",32.5,54.2703706846087,1,343.343167644985,2,0 +"52",23.5,47.9010986100756,1,0,0,0 +"52",23.5,47.9010986100756,1,431.109887490681,1,0 +"52",23.5,47.9010986100756,1,369.550202278316,2,0 +"52",24.5,128.277808806314,1,0,0,0 +"52",24.5,128.277808806314,1,1154.50027925682,1,0 +"52",24.5,128.277808806314,1,456.641464318801,2,0 +"52",25,120.943421288196,1,0,0,0 +"52",25,120.943421288196,1,1088.49079159376,1,0 +"52",25,120.943421288196,1,500.863635547737,2,0 +"52",25,120.943421288196,1,0,0,0 +"52",25,120.943421288196,1,1088.49079159376,1,0 +"52",25,120.943421288196,1,500.863635547737,2,0 +"52",25.5,294.2220680947,1,0,0,0 +"52",25.5,294.2220680947,1,2647.9986128523,1,0 +"52",25.5,294.2220680947,1,604.777061861767,2,0 +"52",26.6,332.539266281263,1,0,0,0 +"52",26.6,332.539266281263,1,2992.85339653137,1,0 +"52",26.6,332.539266281263,1,982.581388039748,2,0 +"52",27,315.206415668493,1,0,0,0 +"52",27,315.206415668493,1,2836.85774101644,1,0 +"52",27,315.206415668493,1,1093.26513150333,2,0 +"52",28.6,266.402555084991,1,0,0,0 +"52",28.6,266.402555084991,1,2397.62299576492,1,0 +"52",28.6,266.402555084991,1,1370.94121205411,2,0 +"52",29.5,248.686439472439,1,0,0,0 +"52",29.5,248.686439472439,1,2238.17795525195,1,0 +"52",29.5,248.686439472439,1,1449.39565717391,2,0 +"52",31.5,222.921963181254,1,0,0,0 +"52",31.5,222.921963181254,1,2006.29766863129,1,0 +"52",31.5,222.921963181254,1,1517.04607532277,2,0 +"52",31.7,221.015930814185,1,0,0,0 +"52",31.7,221.015930814185,1,1989.14337732766,1,0 +"52",31.7,221.015930814185,1,1518.66306445062,2,0 +"52",32.6,213.415700608945,1,0,0,0 +"52",32.6,213.415700608945,1,1920.7413054805,1,0 +"52",32.6,213.415700608945,1,1518.68058288159,2,0 +"53",24,55.7667641269847,1,0,0,0 +"53",24,55.7667641269847,1,501.900877142862,1,0 +"53",24,55.7667641269847,1,430.241673765434,2,0 +"53",25,152.148611714992,1,0,0,0 +"53",25,152.148611714992,1,1369.33750543492,1,0 +"53",25,152.148611714992,1,519.89829209469,2,0 +"53",25.2,170.359727746842,1,0,0,0 +"53",25.2,170.359727746842,1,1533.23754972158,1,0 +"53",25.2,170.359727746842,1,545.095498592316,2,0 +"53",25.7,311.689720049672,1,0,0,0 +"53",25.7,311.689720049672,1,2805.20748044705,1,0 +"53",25.7,311.689720049672,1,670.4072874661,2,0 +"53",25.7,311.689720049672,1,0,0,0 +"53",25.7,311.689720049672,1,2805.20748044705,1,0 +"53",25.7,311.689720049672,1,670.4072874661,2,0 +"53",26,420.18437403102,1,0,0,0 +"53",26,420.18437403102,1,3781.65936627918,1,0 +"53",26,420.18437403102,1,795.341859792134,2,0 +"53",26,420.18437403102,1,0,0,0 +"53",26,420.18437403102,1,3781.65936627918,1,0 +"53",26,420.18437403102,1,795.341859792134,2,0 +"53",26.3,522.771417750107,1,0,0,0 +"53",26.3,522.771417750107,1,4704.94275975096,1,0 +"53",26.3,522.771417750107,1,962.012535976039,2,0 +"53",26.5,540.652732270882,1,0,0,0 +"53",26.5,540.652732270882,1,4865.87459043794,1,0 +"53",26.5,540.652732270882,1,1083.85995162174,2,0 +"53",26.7,606.295546773721,1,0,0,0 +"53",26.7,606.295546773721,1,5456.65992096349,1,0 +"53",26.7,606.295546773721,1,1212.99656050295,2,0 +"53",26.8,640.500098666985,1,0,0,0 +"53",26.8,640.500098666985,1,5764.50088800286,1,0 +"53",26.8,640.500098666985,1,1283.33504485697,2,0 +"53",26.9,674.158168517499,1,0,0,0 +"53",26.9,674.158168517499,1,6067.42351665749,1,0 +"53",26.9,674.158168517499,1,1357.404333184,2,0 +"53",27,707.290398183282,1,0,0,0 +"53",27,707.290398183282,1,6365.61358364954,1,0 +"53",27,707.290398183282,1,1435.03741188819,2,0 +"53",27.3,695.60459736823,1,0,0,0 +"53",27.3,695.60459736823,1,6260.44137631407,1,0 +"53",27.3,695.60459736823,1,1665.80450823042,2,0 +"53",27.7,720.370609679854,1,0,0,0 +"53",27.7,720.370609679854,1,6483.33548711869,1,0 +"53",27.7,720.370609679854,1,1944.95809822312,2,0 +"53",27.75,726.187394173735,1,0,0,0 +"53",27.75,726.187394173735,1,6535.68654756361,1,0 +"53",27.75,726.187394173735,1,1979.94962827917,2,0 +"53",29,646.887818378035,1,0,0,0 +"53",29,646.887818378035,1,5821.99036540232,1,0 +"53",29,646.887818378035,1,2688.67086920634,2,0 +"53",29,646.887818378035,1,0,0,0 +"53",29,646.887818378035,1,5821.99036540232,1,0 +"53",29,646.887818378035,1,2688.67086920634,2,0 +"53",29.3,626.61035663472,1,0,0,0 +"53",29.3,626.61035663472,1,5639.49320971248,1,0 +"53",29.3,626.61035663472,1,2804.32839438058,2,0 +"53",30.3,572.493218432187,1,0,0,0 +"53",30.3,572.493218432187,1,5152.43896588968,1,0 +"53",30.3,572.493218432187,1,3082.07960350974,2,0 +"53",31.7,521.059346003822,1,0,0,0 +"53",31.7,521.059346003822,1,4689.53411403439,1,0 +"53",31.7,521.059346003822,1,3277.97036425124,2,0 +"53",31.75,519.593360765573,1,0,0,0 +"53",31.75,519.593360765573,1,4676.34024689015,1,0 +"53",31.75,519.593360765573,1,3282.0585506101,2,0 +"53",32.3,504.741555743797,1,0,0,0 +"53",32.3,504.741555743797,1,4542.67400169417,1,0 +"53",32.3,504.741555743797,1,3317.16839809502,2,0 +"53",32.8,492.98944607391,1,0,0,0 +"53",32.8,492.98944607391,1,4436.90501466519,1,0 +"53",32.8,492.98944607391,1,3335.65766450499,2,0 +"53",33.1,486.603419861207,1,0,0,0 +"53",33.1,486.603419861207,1,4379.43077875087,1,0 +"53",33.1,486.603419861207,1,3341.70726701349,2,0 +"53",33.25,483.574796465536,1,0,0,0 +"53",33.25,483.574796465536,1,4352.17316818982,1,0 +"53",33.25,483.574796465536,1,3343.49815505791,2,0 +"53",33.3,482.588017184016,1,0,0,0 +"53",33.3,482.588017184016,1,4343.29215465615,1,0 +"53",33.3,482.588017184016,1,3343.92526018294,2,0 +"54",47,12.8790043883461,1,0,0,0 +"54",47,12.8790043883461,1,115.911039495114,1,0 +"54",47,12.8790043883461,1,99.3711747574951,2,0 +"54",143,19.3186075647541,1,0,0,0 +"54",143,19.3186075647541,1,173.867468082787,1,0 +"54",143,19.3186075647541,1,149.057542083337,2,0 +"54",167.7,32.4972592438969,1,0,0,0 +"54",167.7,32.4972592438969,1,292.475333195072,1,0 +"54",167.7,32.4972592438969,1,250.71301238068,2,0 +"54",192.5,19.3333446883903,1,0,0,0 +"54",192.5,19.3333446883903,1,174.000102195513,1,0 +"54",192.5,19.3333446883903,1,149.171250783736,2,0 +"54",195,64.2907292749902,1,0,0,0 +"54",195,64.2907292749902,1,578.616563474912,1,0 +"54",195,64.2907292749902,1,286.812648903575,2,0 +"54",206,40.7306289168609,1,0,0,0 +"54",206,40.7306289168609,1,366.575660251748,1,0 +"54",206,40.7306289168609,1,312.188004354174,2,0 +"54",264,36.3758505447889,1,0,0,0 +"54",264,36.3758505447889,1,327.3826549031,1,0 +"54",264,36.3758505447889,1,280.638454839782,2,0 +"54",290.25,28.7300987456735,1,0,0,0 +"54",290.25,28.7300987456735,1,258.570888711061,1,0 +"54",290.25,28.7300987456735,1,162.287445557478,2,0 +"54",291.1,157.960784793627,1,0,0,0 +"54",291.1,157.960784793627,1,1421.64706314264,1,0 +"54",291.1,157.960784793627,1,291.502657999106,2,0 +"54",296,97.347125766525,1,0,0,0 +"54",296,97.347125766525,1,876.124131898725,1,0 +"54",296,97.347125766525,1,632.230285186872,2,0 +"55",24,27.8833820634924,1,0,0,0 +"55",24,27.8833820634924,1,250.950438571431,1,0 +"55",24,27.8833820634924,1,215.120836882717,2,0 +"55",24,27.8833820634924,1,0,0,0 +"55",24,27.8833820634924,1,250.950438571431,1,0 +"55",24,27.8833820634924,1,215.120836882717,2,0 +"55",25.2,96.8139135159072,1,0,0,0 +"55",25.2,96.8139135159072,1,871.325221643165,1,0 +"55",25.2,96.8139135159072,1,268.927445583892,2,0 +"55",25.8,119.683549485087,1,0,0,0 +"55",25.8,119.683549485087,1,1077.15194536578,1,0 +"55",25.8,119.683549485087,1,338.586490059577,2,0 +"55",26.2,145.609501294998,1,0,0,0 +"55",26.2,145.609501294998,1,1310.48551165498,1,0 +"55",26.2,145.609501294998,1,387.241209908032,2,0 +"55",27.5,141.138435370257,1,0,0,0 +"55",27.5,141.138435370257,1,1270.24591833232,1,0 +"55",27.5,141.138435370257,1,558.368745285599,2,0 +"55",31,107.817012105959,1,0,0,0 +"55",31,107.817012105959,1,970.35310895363,1,0 +"55",31,107.817012105959,1,709.552653238463,2,0 +"55",31.6,104.853983490507,1,0,0,0 +"55",31.6,104.853983490507,1,943.685851414562,1,0 +"55",31.6,104.853983490507,1,713.89726413293,2,0 +"55",32.2,102.287552920732,1,0,0,0 +"55",32.2,102.287552920732,1,920.587976286592,1,0 +"55",32.2,102.287552920732,1,715.251407100033,2,0 +"56",19.75,42.6784860689496,1,0,0,0 +"56",19.75,42.6784860689496,1,384.106374620546,1,0 +"56",19.75,42.6784860689496,1,329.127176055689,2,0 +"56",20.9,107.452670785292,1,0,0,0 +"56",20.9,107.452670785292,1,967.074037067632,1,0 +"56",20.9,107.452670785292,1,407.982083517214,2,0 +"56",24.7,80.1373677221996,1,0,0,0 +"56",24.7,80.1373677221996,1,721.236309499796,1,0 +"56",24.7,80.1373677221996,1,532.708096659404,2,0 +"56",25.4,150.067552609196,1,0,0,0 +"56",25.4,150.067552609196,1,1350.60797348277,1,0 +"56",25.4,150.067552609196,1,574.879126168076,2,0 +"56",26.2,137.72443016009,1,0,0,0 +"56",26.2,137.72443016009,1,1239.51987144081,1,0 +"56",26.2,137.72443016009,1,645.758788825851,2,0 +"56",31,104.323917920289,1,0,0,0 +"56",31,104.323917920289,1,938.915261282604,1,0 +"56",31,104.323917920289,1,749.204902175642,2,0 +"57",24.5,27.592759848218,1,0,0,0 +"57",24.5,27.592759848218,1,248.334838633962,1,0 +"57",24.5,27.592759848218,1,212.882319477361,2,0 +"57",25.75,72.6327984405326,1,0,0,0 +"57",25.75,72.6327984405326,1,653.695185964793,1,0 +"57",25.75,72.6327984405326,1,278.025840412168,2,0 +"57",26,97.6344528704131,1,0,0,0 +"57",26,97.6344528704131,1,878.710075833718,1,0 +"57",26,97.6344528704131,1,295.552787627519,2,0 +"57",26.25,121.464409653533,1,0,0,0 +"57",26.25,121.464409653533,1,1093.17968688179,1,0 +"57",26.25,121.464409653533,1,321.489473025873,2,0 +"57",31,80.1585239696943,1,0,0,0 +"57",31,80.1585239696943,1,721.426715727249,1,0 +"57",31,80.1585239696943,1,534.428889262267,2,0 +"57",32,76.7947874341891,1,0,0,0 +"57",32,76.7947874341891,1,691.153086907702,1,0 +"57",32,76.7947874341891,1,537.258732840742,2,0 +"58",25,27.3052076636085,1,0,0,0 +"58",25,27.3052076636085,1,245.746868972476,1,0 +"58",25,27.3052076636085,1,210.666761454229,2,0 +"58",25.6,79.7295376274436,1,0,0,0 +"58",25.6,79.7295376274436,1,717.565838646992,1,0 +"58",25.6,79.7295376274436,1,228.453894620112,2,0 +"58",30,54.3540770700065,1,0,0,0 +"58",30,54.3540770700065,1,489.186693630059,1,0 +"58",30,54.3540770700065,1,358.244725251945,2,0 +"59",24,27.8833820634924,1,0,0,0 +"59",24,27.8833820634924,1,250.950438571431,1,0 +"59",24,27.8833820634924,1,215.120836882717,2,0 +"59",27,66.7052498994967,1,0,0,0 +"59",27,66.7052498994967,1,600.34724909547,1,0 +"59",27,66.7052498994967,1,308.476273560499,2,0 +"59",31.5,50.8586957536712,1,0,0,0 +"59",31.5,50.8586957536712,1,457.728261783041,1,0 +"59",31.5,50.8586957536712,1,361.151771003977,2,0 +"6",24.5,27.592759848218,1,0,0,0 +"6",24.5,27.592759848218,1,248.334838633962,1,0 +"6",24.5,27.592759848218,1,212.882319477361,2,0 +"6",24.5,27.592759848218,1,0,0,0 +"6",24.5,27.592759848218,1,248.334838633962,1,0 +"6",24.5,27.592759848218,1,212.882319477361,2,0 +"6",25.1,89.9478435808098,1,0,0,0 +"6",25.1,89.9478435808098,1,809.530592227288,1,0 +"6",25.1,89.9478435808098,1,239.211465745349,2,0 +"6",26,168.636070598989,1,0,0,0 +"6",26,168.636070598989,1,1517.7246353909,1,0 +"6",26,168.636070598989,1,385.047283421065,2,0 +"6",26.4,158.804972363463,1,0,0,0 +"6",26.4,158.804972363463,1,1429.24475127116,1,0 +"6",26.4,158.804972363463,1,450.623799635164,2,0 +"6",26.5,156.572136851103,1,0,0,0 +"6",26.5,156.572136851103,1,1409.14923165993,1,0 +"6",26.5,156.572136851103,1,465.200466109181,2,0 +"6",31,107.01334742009,1,0,0,0 +"6",31,107.01334742009,1,963.120126780806,1,0 +"6",31,107.01334742009,1,713.066949360075,2,0 +"6",31.5,104.631681236285,1,0,0,0 +"6",31.5,104.631681236285,1,941.685131126568,1,0 +"6",31.5,104.631681236285,1,715.987260156227,2,0 +"6",31.5,104.631681236285,1,0,0,0 +"6",31.5,104.631681236285,1,941.685131126568,1,0 +"6",31.5,104.631681236285,1,715.987260156227,2,0 +"60",24,39.0367348888893,1,0,0,0 +"60",24,39.0367348888893,1,351.330614000004,1,0 +"60",24,39.0367348888893,1,301.169171635804,2,0 +"60",24,39.0367348888893,1,0,0,0 +"60",24,39.0367348888893,1,351.330614000004,1,0 +"60",24,39.0367348888893,1,301.169171635804,2,0 +"60",24.7,181.82059918912,1,0,0,0 +"60",24.7,181.82059918912,1,1636.38539270208,1,0 +"60",24.7,181.82059918912,1,385.844993462925,2,0 +"60",25.2,228.173607206539,1,0,0,0 +"60",25.2,228.173607206539,1,2053.56246485885,1,0 +"60",25.2,228.173607206539,1,493.817643331251,2,0 +"60",25.5,232.580238678777,1,0,0,0 +"60",25.5,232.580238678777,1,2093.22214810899,1,0 +"60",25.5,232.580238678777,1,569.428457842045,2,0 +"60",26,216.373654864286,1,0,0,0 +"60",26,216.373654864286,1,1947.36289377857,1,0 +"60",26,216.373654864286,1,676.049053160038,2,0 +"60",30,153.155219875896,1,0,0,0 +"60",30,153.155219875896,1,1378.39697888306,1,0 +"60",30,153.155219875896,1,995.978480070587,2,0 +"60",30.2,151.623485098682,1,0,0,0 +"60",30.2,151.623485098682,1,1364.61136588814,1,0 +"60",30.2,151.623485098682,1,999.09731280959,2,0 +"60",31.3,144.442916585929,1,0,0,0 +"60",31.3,144.442916585929,1,1299.98624927336,1,0 +"60",31.3,144.442916585929,1,1006.79059233003,2,0 +"61",25.5,27.0206853431652,1,0,0,0 +"61",25.5,27.0206853431652,1,243.186168088487,1,0 +"61",25.5,27.0206853431652,1,208.473990602306,2,0 +"61",25.75,26.8795483770211,1,0,0,0 +"61",25.75,26.8795483770211,1,241.91593539319,1,0 +"61",25.75,26.8795483770211,1,207.386093508762,2,0 +"61",26.75,124.381323106844,1,0,0,0 +"61",26.75,124.381323106844,1,1119.4319079616,1,0 +"61",26.75,124.381323106844,1,297.875088966023,2,0 +"61",27.15,117.257769962704,1,0,0,0 +"61",27.15,117.257769962704,1,1055.31992966434,1,0 +"61",27.15,117.257769962704,1,345.084978988942,2,0 +"61",31.2,81.8008221663781,1,0,0,0 +"61",31.2,81.8008221663781,1,736.207399497403,1,0 +"61",31.2,81.8008221663781,1,528.695653823956,2,0 +"61",31.3,81.3783400854941,1,0,0,0 +"61",31.3,81.3783400854941,1,732.405060769447,1,0 +"61",31.3,81.3783400854941,1,529.64239056145,2,0 +"62",25,38.2272907290519,1,0,0,0 +"62",25,38.2272907290519,1,344.045616561467,1,0 +"62",25,38.2272907290519,1,294.933466035921,2,0 +"62",26.2,104.289005240932,1,0,0,0 +"62",26.2,104.289005240932,1,938.601047168386,1,0 +"62",26.2,104.289005240932,1,365.641290347732,2,0 +"62",31.7,71.0627507029084,1,0,0,0 +"62",31.7,71.0627507029084,1,639.564756326175,1,0 +"62",31.7,71.0627507029084,1,504.536822528517,2,0 +"63",24,39.0367348888893,1,0,0,0 +"63",24,39.0367348888893,1,351.330614000004,1,0 +"63",24,39.0367348888893,1,301.169171635804,2,0 +"63",24,39.0367348888893,1,0,0,0 +"63",24,39.0367348888893,1,351.330614000004,1,0 +"63",24,39.0367348888893,1,301.169171635804,2,0 +"63",24,39.0367348888893,1,0,0,0 +"63",24,39.0367348888893,1,351.330614000004,1,0 +"63",24,39.0367348888893,1,301.169171635804,2,0 +"63",24.3,99.4067057973146,1,0,0,0 +"63",24.3,99.4067057973146,1,894.660352175831,1,0 +"63",24.3,99.4067057973146,1,311.084142484619,2,0 +"63",24.5,157.725069568133,1,0,0,0 +"63",24.5,157.725069568133,1,1419.5256261132,1,0 +"63",24.5,157.725069568133,1,337.20543183776,2,0 +"63",24.5,157.725069568133,1,0,0,0 +"63",24.5,157.725069568133,1,1419.5256261132,1,0 +"63",24.5,157.725069568133,1,337.20543183776,2,0 +"63",24.7,183.303422225215,1,0,0,0 +"63",24.7,183.303422225215,1,1649.73080002694,1,0 +"63",24.7,183.303422225215,1,375.058969528383,2,0 +"63",24.7,183.303422225215,1,0,0,0 +"63",24.7,183.303422225215,1,1649.73080002694,1,0 +"63",24.7,183.303422225215,1,375.058969528383,2,0 +"63",25,239.769170167077,1,0,0,0 +"63",25,239.769170167077,1,2157.9225315037,1,0 +"63",25,239.769170167077,1,445.207879308168,2,0 +"63",25,239.769170167077,1,0,0,0 +"63",25,239.769170167077,1,2157.9225315037,1,0 +"63",25,239.769170167077,1,445.207879308168,2,0 +"63",25.75,346.185124220092,1,0,0,0 +"63",25.75,346.185124220092,1,3115.66611798082,1,0 +"63",25.75,346.185124220092,1,707.885142164267,2,0 +"63",26.1,360.3267141151,1,0,0,0 +"63",26.1,360.3267141151,1,3242.9404270359,1,0 +"63",26.1,360.3267141151,1,838.211075258659,2,0 +"63",26.1,360.3267141151,1,0,0,0 +"63",26.1,360.3267141151,1,3242.9404270359,1,0 +"63",26.1,360.3267141151,1,838.211075258659,2,0 +"63",26.4,414.596728081616,1,0,0,0 +"63",26.4,414.596728081616,1,3731.37055273455,1,0 +"63",26.4,414.596728081616,1,959.462351241395,2,0 +"63",27.1,404.564046466737,1,0,0,0 +"63",27.1,404.564046466737,1,3641.07641820063,1,0 +"63",27.1,404.564046466737,1,1247.21996106822,2,0 +"63",30.75,291.010863068601,1,0,0,0 +"63",30.75,291.010863068601,1,2619.09776761741,1,0 +"63",30.75,291.010863068601,1,1839.57797607453,2,0 +"63",31.25,283.414337509149,1,0,0,0 +"63",31.25,283.414337509149,1,2550.72903758234,1,0 +"63",31.25,283.414337509149,1,1857.69968254038,2,0 +"63",31.25,283.414337509149,1,0,0,0 +"63",31.25,283.414337509149,1,2550.72903758234,1,0 +"63",31.25,283.414337509149,1,1857.69968254038,2,0 +"63",31.75,276.760313425396,1,0,0,0 +"63",31.75,276.760313425396,1,2490.84282082856,1,0 +"63",31.75,276.760313425396,1,1868.5830034262,2,0 +"63",31.9,274.92110317852,1,0,0,0 +"63",31.9,274.92110317852,1,2474.28992860668,1,0 +"63",31.9,274.92110317852,1,1870.65455136842,2,0 +"64",27.1,26.1301398736827,1,0,0,0 +"64",27.1,26.1301398736827,1,235.171258863145,1,0 +"64",27.1,26.1301398736827,1,201.608070875004,2,0 +"64",28.1,73.4533686086287,1,0,0,0 +"64",28.1,73.4533686086287,1,661.080317477658,1,0 +"64",28.1,73.4533686086287,1,253.161845249394,2,0 +"64",36.7,45.3936024265308,1,0,0,0 +"64",36.7,45.3936024265308,1,408.542421838777,1,0 +"64",36.7,45.3936024265308,1,341.722598299061,2,0 +"65",22.2,20.2690105957445,1,0,0,0 +"65",22.2,20.2690105957445,1,182.421095361701,1,0 +"65",22.2,20.2690105957445,1,156.360298604996,2,0 +"65",23.8,63.0770808903416,1,0,0,0 +"65",23.8,63.0770808903416,1,567.693728013075,1,0 +"65",23.8,63.0770808903416,1,237.662062821276,2,0 +"65",24,61.6178807833417,1,0,0,0 +"65",24,61.6178807833417,1,554.560927050076,1,0 +"65",24,61.6178807833417,1,246.431161701026,2,0 +"65",24.5,88.2445181673651,1,0,0,0 +"65",24.5,88.2445181673651,1,794.200663506286,1,0 +"65",24.5,88.2445181673651,1,274.180387506911,2,0 +"65",25,112.452211900286,1,0,0,0 +"65",25,112.452211900286,1,1012.06990710257,1,0 +"65",25,112.452211900286,1,318.995487387705,2,0 +"65",25.8,149.629854966071,1,0,0,0 +"65",25.8,149.629854966071,1,1346.66869469464,1,0 +"65",25.8,149.629854966071,1,433.947672245053,2,0 +"65",25.9,147.548986436592,1,0,0,0 +"65",25.9,147.548986436592,1,1327.94087792933,1,0 +"65",25.9,147.548986436592,1,447.475088380311,2,0 +"65",26,145.545052140329,1,0,0,0 +"65",26,145.545052140329,1,1309.90546926296,1,0 +"65",26,145.545052140329,1,460.381571112385,2,0 +"65",26.5,166.418811423342,1,0,0,0 +"65",26.5,166.418811423342,1,1497.76930281008,1,0 +"65",26.5,166.418811423342,1,525.738683026776,2,0 +"65",26.5,166.418811423342,1,0,0,0 +"65",26.5,166.418811423342,1,1497.76930281008,1,0 +"65",26.5,166.418811423342,1,525.738683026776,2,0 +"65",27.25,177.386586677497,1,0,0,0 +"65",27.25,177.386586677497,1,1596.47928009747,1,0 +"65",27.25,177.386586677497,1,627.755703352703,2,0 +"65",28.4,172.365272831096,1,0,0,0 +"65",28.4,172.365272831096,1,1551.28745547987,1,0 +"65",28.4,172.365272831096,1,774.756122583491,2,0 +"65",28.5,170.642629699545,1,0,0,0 +"65",28.5,170.642629699545,1,1535.78366729591,1,0 +"65",28.5,170.642629699545,1,784.257449713419,2,0 +"65",30.3,147.810295917978,1,0,0,0 +"65",30.3,147.810295917978,1,1330.2926632618,1,0 +"65",30.3,147.810295917978,1,890.142839365217,2,0 +"65",30.4,146.886956761603,1,0,0,0 +"65",30.4,146.886956761603,1,1321.98261085443,1,0 +"65",30.4,146.886956761603,1,893.29577066293,2,0 +"65",31,141.880306524449,1,0,0,0 +"65",31,141.880306524449,1,1276.92275872004,1,0 +"65",31,141.880306524449,1,908.050203213131,2,0 +"65",32.7,131.339283596052,1,0,0,0 +"65",32.7,131.339283596052,1,1182.05355236446,1,0 +"65",32.7,131.339283596052,1,921.85453112446,2,0 +"66",24.5,27.592759848218,1,0,0,0 +"66",24.5,27.592759848218,1,248.334838633962,1,0 +"66",24.5,27.592759848218,1,212.882319477361,2,0 +"66",26.4,67.6499936194952,1,0,0,0 +"66",26.4,67.6499936194952,1,608.849942575457,1,0 +"66",26.4,67.6499936194952,1,306.935991310646,2,0 +"66",30.9,51.2967783580851,1,0,0,0 +"66",30.9,51.2967783580851,1,461.671005222766,1,0 +"66",30.9,51.2967783580851,1,363.193301883461,2,0 +"67",24,24.3225680658362,1,0,0,0 +"67",24,24.3225680658362,1,218.903112592525,1,0 +"67",24,24.3225680658362,1,187.638342204725,2,0 +"67",25.5,84.9429199490464,1,0,0,0 +"67",25.5,84.9429199490464,1,764.486279541418,1,0 +"67",25.5,84.9429199490464,1,299.38553283352,2,0 +"67",30,60.3015378657426,1,0,0,0 +"67",30,60.3015378657426,1,542.713840791683,1,0 +"67",30,60.3015378657426,1,411.299070127053,2,0 +"68",47,10.732503656955,1,0,0,0 +"68",47,10.732503656955,1,96.5925329125954,1,0 +"68",47,10.732503656955,1,82.8093122979126,2,0 +"68",47.8,61.2424707810961,1,0,0,0 +"68",47.8,61.2424707810961,1,551.182237029865,1,0 +"68",47.8,61.2424707810961,1,116.923927753921,2,0 +"68",48,59.279217353211,1,0,0,0 +"68",48,59.279217353211,1,533.512956178899,1,0 +"68",48,59.279217353211,1,130.375834186744,2,0 +"68",48,59.279217353211,1,0,0,0 +"68",48,59.279217353211,1,533.512956178899,1,0 +"68",48,59.279217353211,1,130.375834186744,2,0 +"68",48,59.279217353211,1,0,0,0 +"68",48,59.279217353211,1,533.512956178899,1,0 +"68",48,59.279217353211,1,130.375834186744,2,0 +"68",48,59.279217353211,1,0,0,0 +"68",48,59.279217353211,1,533.512956178899,1,0 +"68",48,59.279217353211,1,130.375834186744,2,0 +"68",48.1,69.3464547586754,1,0,0,0 +"68",48.1,69.3464547586754,1,624.118092828078,1,0 +"68",48.1,69.3464547586754,1,137.519170875286,2,0 +"68",49,245.598346011157,1,0,0,0 +"68",49,245.598346011157,1,2210.38511410042,1,0 +"68",49,245.598346011157,1,385.342755290496,2,0 +"68",49.1,241.380747514712,1,0,0,0 +"68",49.1,241.380747514712,1,2172.42672763241,1,0 +"68",49.1,241.380747514712,1,414.779486727227,2,0 +"68",49.5,226.05476469529,1,0,0,0 +"68",49.5,226.05476469529,1,2034.49288225761,1,0 +"68",49.5,226.05476469529,1,520.020487783131,2,0 +"68",49.5,226.05476469529,1,0,0,0 +"68",49.5,226.05476469529,1,2034.49288225761,1,0 +"68",49.5,226.05476469529,1,520.020487783131,2,0 +"68",49.5,226.05476469529,1,0,0,0 +"68",49.5,226.05476469529,1,2034.49288225761,1,0 +"68",49.5,226.05476469529,1,520.020487783131,2,0 +"68",51,185.466998117371,1,0,0,0 +"68",51,185.466998117371,1,1669.20298305634,1,0 +"68",51,185.466998117371,1,778.275694318555,2,0 +"68",51.5,176.191057508208,1,0,0,0 +"68",51.5,176.191057508208,1,1585.71951757387,1,0 +"68",51.5,176.191057508208,1,830.13805103512,2,0 +"68",53.6,150.535227264907,1,0,0,0 +"68",53.6,150.535227264907,1,1354.81704538416,1,0 +"68",53.6,150.535227264907,1,942.023857151309,2,0 +"68",54,147.261455434191,1,0,0,0 +"68",54,147.261455434191,1,1325.35309890772,1,0 +"68",54,147.261455434191,1,950.64631922812,2,0 +"68",54.75,141.996303808587,1,0,0,0 +"68",54.75,141.996303808587,1,1277.96673427728,1,0 +"68",54.75,141.996303808587,1,960.089976993585,2,0 +"69",25.5,43.2330965490643,1,0,0,0 +"69",25.5,43.2330965490643,1,389.097868941579,1,0 +"69",25.5,43.2330965490643,1,333.55838496369,2,0 +"69",28.25,100.09511926953,1,0,0,0 +"69",28.25,100.09511926953,1,900.856073425767,1,0 +"69",28.25,100.09511926953,1,521.077677235682,2,0 +"69",31.25,83.6422954932054,1,0,0,0 +"69",31.25,83.6422954932054,1,752.780659438849,1,0 +"69",31.25,83.6422954932054,1,573.933415490299,2,0 +"7",24.1,38.8185542908069,1,0,0,0 +"7",24.1,38.8185542908069,1,349.366988617262,1,0 +"7",24.1,38.8185542908069,1,215.536322054296,2,0 +"7",25.2,121.215007636759,1,0,0,0 +"7",25.2,121.215007636759,1,1090.93506873083,1,0 +"7",25.2,121.215007636759,1,333.30769504384,2,0 +"7",26,109.030943254404,1,0,0,0 +"7",26,109.030943254404,1,981.278489289637,1,0 +"7",26,109.030943254404,1,410.814740483149,2,0 +"7",27.1,145.016977747708,1,0,0,0 +"7",27.1,145.016977747708,1,1305.15279972937,1,0 +"7",27.1,145.016977747708,1,533.912457153572,2,0 +"7",28.1,178.930051418354,1,0,0,0 +"7",28.1,178.930051418354,1,1610.37046276518,1,0 +"7",28.1,178.930051418354,1,668.898083888896,2,0 +"7",30.2,146.928137166407,1,0,0,0 +"7",30.2,146.928137166407,1,1322.35323449766,1,0 +"7",30.2,146.928137166407,1,838.574540476326,2,0 +"7",33.3,125.346754156318,1,0,0,0 +"7",33.3,125.346754156318,1,1128.12078740686,1,0 +"7",33.3,125.346754156318,1,886.690301646467,2,0 +"7",51.3,116.839811933645,1,0,0,0 +"7",51.3,116.839811933645,1,1051.5583074028,1,0 +"7",51.3,116.839811933645,1,781.899879270112,2,0 +"7",52.75,155.893740636733,1,0,0,0 +"7",52.75,155.893740636733,1,1403.0436657306,1,0 +"7",52.75,155.893740636733,1,855.329116754325,2,0 +"7",57.25,125.278670378928,1,0,0,0 +"7",57.25,125.278670378928,1,1127.50803341035,1,0 +"7",57.25,125.278670378928,1,913.935722181442,2,0 +"70",23.5,19.7239817806194,1,0,0,0 +"70",23.5,19.7239817806194,1,177.515836025574,1,0 +"70",23.5,19.7239817806194,1,152.167730349895,2,0 +"70",24.5,52.8202742143645,1,0,0,0 +"70",24.5,52.8202742143645,1,475.38246792928,1,0 +"70",24.5,52.8202742143645,1,188.028838248918,2,0 +"70",29,37.586350263324,1,0,0,0 +"70",29,37.586350263324,1,338.277152369916,1,0 +"70",29,37.586350263324,1,256.727168192792,2,0 +"71",26.1,45.3614473353802,1,0,0,0 +"71",26.1,45.3614473353802,1,408.253026018422,1,0 +"71",26.1,45.3614473353802,1,349.983226664784,2,0 +"71",27.4,121.246765244035,1,0,0,0 +"71",27.4,121.246765244035,1,1091.22088719632,1,0 +"71",27.4,121.246765244035,1,465.411443048036,2,0 +"71",30.5,93.9563046863884,1,0,0,0 +"71",30.5,93.9563046863884,1,845.606742177495,1,0 +"71",30.5,93.9563046863884,1,596.773213101269,2,0 +"72",24.5,27.592759848218,1,0,0,0 +"72",24.5,27.592759848218,1,248.334838633962,1,0 +"72",24.5,27.592759848218,1,212.882319477361,2,0 +"72",25,80.0706042206554,1,0,0,0 +"72",25,80.0706042206554,1,720.635437985899,1,0 +"72",25,80.0706042206554,1,231.082501130778,2,0 +"72",25.7,123.75515817351,1,0,0,0 +"72",25.7,123.75515817351,1,1113.79642356159,1,0 +"72",25.7,123.75515817351,1,310.883855489391,2,0 +"72",26,118.441910761271,1,0,0,0 +"72",26,118.441910761271,1,1065.97719685144,1,0 +"72",26,118.441910761271,1,345.993036960708,2,0 +"72",29.3,86.1285148243663,1,0,0,0 +"72",29.3,86.1285148243663,1,775.156633419297,1,0 +"72",29.3,86.1285148243663,1,522.169679030951,2,0 +"72",30.8,79.5578151005691,1,0,0,0 +"72",30.8,79.5578151005691,1,716.020335905122,1,0 +"72",30.8,79.5578151005691,1,537.930972564596,2,0 +"73",15.6,23.3009982985774,1,0,0,0 +"73",15.6,23.3009982985774,1,209.708984687197,1,0 +"73",15.6,23.3009982985774,1,179.302374003487,2,0 +"73",16.1,112.754076210007,1,0,0,0 +"73",16.1,112.754076210007,1,1014.78668589006,1,0 +"73",16.1,112.754076210007,1,212.186085835514,2,0 +"73",19.5,76.3902447507198,1,0,0,0 +"73",19.5,76.3902447507198,1,687.512202756478,1,0 +"73",19.5,76.3902447507198,1,431.257733864379,2,0 +"73",22.7,64.7301218858679,1,0,0,0 +"73",22.7,64.7301218858679,1,582.571096972811,1,0 +"73",22.7,64.7301218858679,1,458.092748872387,2,0 +"73",24,61.9679213443419,1,0,0,0 +"73",24,61.9679213443419,1,557.711292099077,1,0 +"73",24,61.9679213443419,1,454.152964755074,2,0 +"73",24.3,61.4109237598705,1,0,0,0 +"73",24.3,61.4109237598705,1,552.698313838834,1,0 +"73",24.3,61.4109237598705,1,452.688770746043,2,0 +"73",25,149.899365353576,1,0,0,0 +"73",25,149.899365353576,1,1349.09428818219,1,0 +"73",25,149.899365353576,1,483.415291810432,2,0 +"73",26,244.371087753484,1,0,0,0 +"73",26,244.371087753484,1,2199.33978978136,1,0 +"73",26,244.371087753484,1,688.768086091699,2,0 +"73",26,244.371087753484,1,0,0,0 +"73",26,244.371087753484,1,2199.33978978136,1,0 +"73",26,244.371087753484,1,688.768086091699,2,0 +"73",26.1,267.308251498484,1,0,0,0 +"73",26.1,267.308251498484,1,2405.77426348635,1,0 +"73",26.1,267.308251498484,1,713.377969366256,2,0 +"73",26.3,296.702704658257,1,0,0,0 +"73",26.3,296.702704658257,1,2670.32434192432,1,0 +"73",26.3,296.702704658257,1,769.081783875855,2,0 +"73",26.5,355.614202963591,1,0,0,0 +"73",26.5,355.614202963591,1,3200.52782667232,1,0 +"73",26.5,355.614202963591,1,836.034428960357,2,0 +"73",27.25,446.202026951295,1,0,0,0 +"73",27.25,446.202026951295,1,4015.81824256165,1,0 +"73",27.25,446.202026951295,1,1177.51077017272,2,0 +"73",27.5,430.424327634589,1,0,0,0 +"73",27.5,430.424327634589,1,3873.8189487113,1,0 +"73",27.5,430.424327634589,1,1281.1685657388,2,0 +"73",27.5,430.424327634589,1,0,0,0 +"73",27.5,430.424327634589,1,3873.8189487113,1,0 +"73",27.5,430.424327634589,1,1281.1685657388,2,0 +"73",27.8,413.353473580978,1,0,0,0 +"73",27.8,413.353473580978,1,3720.1812622288,1,0 +"73",27.8,413.353473580978,1,1390.52472200392,2,0 +"73",30.5,320.110757989685,1,0,0,0 +"73",30.5,320.110757989685,1,2880.99682190716,1,0 +"73",30.5,320.110757989685,1,1889.92009179343,2,0 +"73",31,310.251305458318,1,0,0,0 +"73",31,310.251305458318,1,2792.26174912487,1,0 +"73",31,310.251305458318,1,1923.52002981992,2,0 +"73",31.25,305.839499093642,1,0,0,0 +"73",31.25,305.839499093642,1,2752.55549184278,1,0 +"73",31.25,305.839499093642,1,1936.27462173836,2,0 +"73",31.8,297.151554796281,1,0,0,0 +"73",31.8,297.151554796281,1,2674.36399316653,1,0 +"73",31.8,297.151554796281,1,1956.4491153878,2,0 +"73",32,294.295167068181,1,0,0,0 +"73",32,294.295167068181,1,2648.65650361362,1,0 +"73",32,294.295167068181,1,1961.45683072579,2,0 +"74",23.6,39.3653662367874,1,0,0,0 +"74",23.6,39.3653662367874,1,354.288296131086,1,0 +"74",23.6,39.3653662367874,1,303.699603863368,2,0 +"74",23.7,39.2829452179982,1,0,0,0 +"74",23.7,39.2829452179982,1,353.546506961984,1,0 +"74",23.7,39.2829452179982,1,303.065048095483,2,0 +"74",24.8,240.170908526578,1,0,0,0 +"74",24.8,240.170908526578,1,2161.5381767392,1,0 +"74",24.8,240.170908526578,1,524.766944463295,2,0 +"74",25.9,205.705918170488,1,0,0,0 +"74",25.9,205.705918170488,1,1851.35326353439,1,0 +"74",25.9,205.705918170488,1,749.578770164411,2,0 +"74",26.5,192.066769306435,1,0,0,0 +"74",26.5,192.066769306435,1,1728.60092375791,1,0 +"74",26.5,192.066769306435,1,830.616974270261,2,0 +"74",27.7,236.728073575902,1,0,0,0 +"74",27.7,236.728073575902,1,2130.55266218312,1,0 +"74",27.7,236.728073575902,1,1028.9892041026,2,0 +"74",28,229.625190395084,1,0,0,0 +"74",28,229.625190395084,1,2066.62671355576,1,0 +"74",28,229.625190395084,1,1068.43820004057,2,0 +"74",29.6,201.877633125288,1,0,0,0 +"74",29.6,201.877633125288,1,1816.89869812759,1,0 +"74",29.6,201.877633125288,1,1198.02735642698,2,0 +"74",29.8,199.307462147244,1,0,0,0 +"74",29.8,199.307462147244,1,1793.7671593252,1,0 +"74",29.8,199.307462147244,1,1207.11832580422,2,0 +"74",30.2,194.60412231047,1,0,0,0 +"74",30.2,194.60412231047,1,1751.43710079423,1,0 +"74",30.2,194.60412231047,1,1221.88100508154,2,0 +"74",34,167.038577913701,1,0,0,0 +"74",34,167.038577913701,1,1503.3472012233,1,0 +"74",34,167.038577913701,1,1231.97594836868,2,0 +"75",23.1,39.780126461091,1,0,0,0 +"75",23.1,39.780126461091,1,358.021138149819,1,0 +"75",23.1,39.780126461091,1,306.891927695466,2,0 +"75",25.1,94.875743708525,1,0,0,0 +"75",25.1,94.875743708525,1,853.881693376725,1,0 +"75",25.1,94.875743708525,1,443.340986107464,2,0 +"75",29.3,73.3654374566187,1,0,0,0 +"75",29.3,73.3654374566187,1,660.288937109568,1,0 +"75",29.3,73.3654374566187,1,516.436639629332,2,0 +"76",24.3,27.7086380068948,1,0,0,0 +"76",24.3,27.7086380068948,1,249.377742062053,1,0 +"76",24.3,27.7086380068948,1,213.774962101268,2,0 +"76",25.7,90.4049232714497,1,0,0,0 +"76",25.7,90.4049232714497,1,813.644309443047,1,0 +"76",25.7,90.4049232714497,1,310.39889241657,2,0 +"76",26.6,81.5106194919264,1,0,0,0 +"76",26.6,81.5106194919264,1,733.595575427337,1,0 +"76",26.6,81.5106194919264,1,363.447240928893,2,0 +"76",27.4,126.359581624215,1,0,0,0 +"76",27.4,126.359581624215,1,1137.23623461793,1,0 +"76",27.4,126.359581624215,1,429.523123291182,2,0 +"76",28.1,166.974402149437,1,0,0,0 +"76",28.1,166.974402149437,1,1502.76961934493,1,0 +"76",28.1,166.974402149437,1,526.002685492161,2,0 +"76",29,149.666629934464,1,0,0,0 +"76",29,149.666629934464,1,1346.99966941018,1,0 +"76",29,149.666629934464,1,632.05183917769,2,0 +"76",29.3,156.039098711354,1,0,0,0 +"76",29.3,156.039098711354,1,1404.35188840219,1,0 +"76",29.3,156.039098711354,1,659.038482047422,2,0 +"76",30.25,180.65086059742,1,0,0,0 +"76",30.25,180.65086059742,1,1625.85774537678,1,0 +"76",30.25,180.65086059742,1,777.248904609228,2,0 +"76",31.1,166.510117365923,1,0,0,0 +"76",31.1,166.510117365923,1,1498.59105629331,1,0 +"76",31.1,166.510117365923,1,852.979105927809,2,0 +"76",33.3,143.860034293757,1,0,0,0 +"76",33.3,143.860034293757,1,1294.74030864381,1,0 +"76",33.3,143.860034293757,1,938.311657633391,2,0 +"76",34,139.174936400658,1,0,0,0 +"76",34,139.174936400658,1,1252.57442760592,1,0 +"76",34,139.174936400658,1,945.822782088765,2,0 +"77",23.5,39.4479635612387,1,0,0,0 +"77",23.5,39.4479635612387,1,355.031672051149,1,0 +"77",23.5,39.4479635612387,1,304.33546069979,2,0 +"77",25.1,105.157971523688,1,0,0,0 +"77",25.1,105.157971523688,1,946.421743713193,1,0 +"77",25.1,105.157971523688,1,372.339375270752,2,0 +"77",30.1,73.2065521321354,1,0,0,0 +"77",30.1,73.2065521321354,1,658.858969189218,1,0 +"77",30.1,73.2065521321354,1,510.87102670557,2,0 +"78",25.5,37.8289594804313,1,0,0,0 +"78",25.5,37.8289594804313,1,340.460635323882,1,0 +"78",25.5,37.8289594804313,1,291.863586843229,2,0 +"78",27.3,99.9264918609343,1,0,0,0 +"78",27.3,99.9264918609343,1,899.338426748408,1,0 +"78",27.3,99.9264918609343,1,383.867771254835,2,0 +"78",32.7,70.0060813829397,1,0,0,0 +"78",32.7,70.0060813829397,1,630.054732446457,1,0 +"78",32.7,70.0060813829397,1,499.903986110385,2,0 +"79",25.1,46.3217055001353,1,0,0,0 +"79",25.1,46.3217055001353,1,416.895349501218,1,0 +"79",25.1,46.3217055001353,1,357.384863050054,2,0 +"79",25.6,45.8390398196743,1,0,0,0 +"79",25.6,45.8390398196743,1,412.551358377069,1,0 +"79",25.6,45.8390398196743,1,353.664863404057,2,0 +"79",27.1,207.220684566726,1,0,0,0 +"79",27.1,207.220684566726,1,1864.98616110053,1,0 +"79",27.1,207.220684566726,1,531.736612835407,2,0 +"79",27.25,202.6862320547,1,0,0,0 +"79",27.25,202.6862320547,1,1824.1760884923,1,0 +"79",27.25,202.6862320547,1,561.787761529625,2,0 +"79",32.1,134.367466035409,1,0,0,0 +"79",32.1,134.367466035409,1,1209.30719431869,1,0 +"79",32.1,134.367466035409,1,905.55890030249,2,0 +"79",32.75,130.692438828045,1,0,0,0 +"79",32.75,130.692438828045,1,1176.2319494524,1,0 +"79",32.75,130.692438828045,1,908.494187124893,2,0 +"8",24.1,47.3025170245653,1,0,0,0 +"8",24.1,47.3025170245653,1,425.722653221088,1,0 +"8",24.1,47.3025170245653,1,364.941191003056,2,0 +"8",25.3,126.751827077026,1,0,0,0 +"8",25.3,126.751827077026,1,1140.76644369324,1,0 +"8",25.3,126.751827077026,1,455.933182391163,2,0 +"8",25.75,183.961357549766,1,0,0,0 +"8",25.75,183.961357549766,1,1655.65221794789,1,0 +"8",25.75,183.961357549766,1,512.661050875508,2,0 +"8",27,180.022683940483,1,0,0,0 +"8",27,180.022683940483,1,1620.20415546435,1,0 +"8",27,180.022683940483,1,718.496416302267,2,0 +"8",28.75,153.116342838089,1,0,0,0 +"8",28.75,153.116342838089,1,1378.0470855428,1,0 +"8",28.75,153.116342838089,1,859.453532185028,2,0 +"8",29.25,237.600381779355,1,0,0,0 +"8",29.25,237.600381779355,1,2138.4034360142,1,0 +"8",29.25,237.600381779355,1,914.787567177693,2,0 +"8",31.7,191.483537340836,1,0,0,0 +"8",31.7,191.483537340836,1,1723.35183606752,1,0 +"8",31.7,191.483537340836,1,1148.5806009749,2,0 +"8",31.75,190.87530299235,1,0,0,0 +"8",31.75,190.87530299235,1,1717.87772693115,1,0 +"8",31.75,190.87530299235,1,1150.70908366132,2,0 +"8",34.75,165.976066143098,1,0,0,0 +"8",34.75,165.976066143098,1,1493.78459528788,1,0 +"8",34.75,165.976066143098,1,1189.06882081446,2,0 +"80",93.4,36.7619689631344,1,0,0,0 +"80",93.4,36.7619689631344,1,330.857720668209,1,0 +"80",93.4,36.7619689631344,1,283.646149747848,2,0 +"80",94.75,100.939786327752,1,0,0,0 +"80",94.75,100.939786327752,1,908.458076949764,1,0 +"80",94.75,100.939786327752,1,366.567425942382,2,0 +"80",96,88.4147615110871,1,0,0,0 +"80",96,88.4147615110871,1,795.732853599784,1,0 +"80",96,88.4147615110871,1,438.074555786329,2,0 +"80",97.7,137.433661301623,1,0,0,0 +"80",97.7,137.433661301623,1,1236.90295171461,1,0 +"80",97.7,137.433661301623,1,613.341912569303,2,0 +"80",100,114.878105804546,1,0,0,0 +"80",100,114.878105804546,1,1033.90295224092,1,0 +"80",100,114.878105804546,1,715.912542558762,2,0 +"81",23.9,19.5593150238898,1,0,0,0 +"81",23.9,19.5593150238898,1,176.033835215008,1,0 +"81",23.9,19.5593150238898,1,150.89991679156,2,0 +"81",25,52.6189876851378,1,0,0,0 +"81",25,52.6189876851378,1,473.57088916624,1,0 +"81",25,52.6189876851378,1,186.477947632726,2,0 +"81",25.7,48.5391523728217,1,0,0,0 +"81",25.7,48.5391523728217,1,436.852371355396,1,0 +"81",25.7,48.5391523728217,1,210.82579376884,2,0 +"81",25.8,51.9041889336801,1,0,0,0 +"81",25.8,51.9041889336801,1,467.137700403121,1,0 +"81",25.8,51.9041889336801,1,213.81654483054,2,0 +"81",26.8,110.564102994421,1,0,0,0 +"81",26.8,110.564102994421,1,995.076926949791,1,0 +"81",26.8,110.564102994421,1,315.405989849432,2,0 +"81",26.9,109.01364317801,1,0,0,0 +"81",26.9,109.01364317801,1,981.12278860209,1,0 +"81",26.9,109.01364317801,1,325.517688574861,2,0 +"81",27.6,135.837356741265,1,0,0,0 +"81",27.6,135.837356741265,1,1222.53621067138,1,0 +"81",27.6,135.837356741265,1,404.026885734816,2,0 +"81",28.3,124.137014906068,1,0,0,0 +"81",28.3,124.137014906068,1,1117.23313415461,1,0 +"81",28.3,124.137014906068,1,477.545222930658,2,0 +"81",29.5,109.851874921498,1,0,0,0 +"81",29.5,109.851874921498,1,988.666874293481,1,0 +"81",29.5,109.851874921498,1,557.185482588009,2,0 +"81",32.1,92.8622829654851,1,0,0,0 +"81",32.1,92.8622829654851,1,835.760546689366,1,0 +"81",32.1,92.8622829654851,1,618.860862640071,2,0 +"81",32.5,91.1898584787143,1,0,0,0 +"81",32.5,91.1898584787143,1,820.708726308429,1,0 +"81",32.5,91.1898584787143,1,621.030959895204,2,0 +"81",33.8,86.6963452328885,1,0,0,0 +"81",33.8,86.6963452328885,1,780.267107095996,1,0 +"81",33.8,86.6963452328885,1,621.050768233878,2,0 +"82",21.1,41.484683144197,1,0,0,0 +"82",21.1,41.484683144197,1,373.362148297773,1,0 +"82",21.1,41.484683144197,1,319.989279365774,2,0 +"82",22.1,107.633830091788,1,0,0,0 +"82",22.1,107.633830091788,1,968.70447082609,1,0 +"82",22.1,107.633830091788,1,391.397227985242,2,0 +"82",24,89.3324642824928,1,0,0,0 +"82",24,89.3324642824928,1,803.992178542435,1,0 +"82",24,89.3324642824928,1,491.288254087734,2,0 +"82",24.25,125.570981985674,1,0,0,0 +"82",24.25,125.570981985674,1,1130.13883787107,1,0 +"82",24.25,125.570981985674,1,505.726065225904,2,0 +"82",25.5,206.564365949514,1,0,0,0 +"82",25.5,206.564365949514,1,1859.07929354562,1,0 +"82",25.5,206.564365949514,1,736.902942618318,2,0 +"82",26,194.771637770544,1,0,0,0 +"82",26,194.771637770544,1,1752.94473993489,1,0 +"82",26,194.771637770544,1,807.952005642654,2,0 +"82",26,194.771637770544,1,0,0,0 +"82",26,194.771637770544,1,1752.94473993489,1,0 +"82",26,194.771637770544,1,807.952005642654,2,0 +"82",26.9,249.870027471864,1,0,0,0 +"82",26.9,249.870027471864,1,2248.83024724678,1,0 +"82",26.9,249.870027471864,1,943.066748895453,2,0 +"82",27.1,259.488303943404,1,0,0,0 +"82",27.1,259.488303943404,1,2335.39473549063,1,0 +"82",27.1,259.488303943404,1,978.94556543215,2,0 +"82",27.25,277.915279815222,1,0,0,0 +"82",27.25,277.915279815222,1,2501.237518337,1,0 +"82",27.25,277.915279815222,1,1008.99383997171,2,0 +"82",29.5,251.641623476866,1,0,0,0 +"82",29.5,251.641623476866,1,2264.7746112918,1,0 +"82",29.5,251.641623476866,1,1376.37655286588,2,0 +"82",30.5,234.976875587818,1,0,0,0 +"82",30.5,234.976875587818,1,2114.79188029037,1,0 +"82",30.5,234.976875587818,1,1441.35237225123,2,0 +"82",31,228.347808366588,1,0,0,0 +"82",31,228.347808366588,1,2055.13027529929,1,0 +"82",31,228.347808366588,1,1460.4869222319,2,0 +"82",32.75,210.949375808583,1,0,0,0 +"82",32.75,210.949375808583,1,1898.54438227725,1,0 +"82",32.75,210.949375808583,1,1482.91829377464,2,0 +"82",33,208.988910950992,1,0,0,0 +"82",33,208.988910950992,1,1880.90019855893,1,0 +"82",33,208.988910950992,1,1482.19091044002,2,0 +"83",24,39.0367348888893,1,0,0,0 +"83",24,39.0367348888893,1,351.330614000004,1,0 +"83",24,39.0367348888893,1,301.169171635804,2,0 +"83",24,39.0367348888893,1,0,0,0 +"83",24,39.0367348888893,1,351.330614000004,1,0 +"83",24,39.0367348888893,1,301.169171635804,2,0 +"83",24.4,158.090664279073,1,0,0,0 +"83",24.4,158.090664279073,1,1422.81597851166,1,0 +"83",24.4,158.090664279073,1,335.769847634489,2,0 +"83",24.75,178.971691133438,1,0,0,0 +"83",24.75,178.971691133438,1,1610.74522020094,1,0 +"83",24.75,178.971691133438,1,405.832243515105,2,0 +"83",25,172.248660011499,1,0,0,0 +"83",25,172.248660011499,1,1550.23794010349,1,0 +"83",25,172.248660011499,1,450.978324494981,2,0 +"83",25.2,213.165503840876,1,0,0,0 +"83",25.2,213.165503840876,1,1918.48953456789,1,0 +"83",25.2,213.165503840876,1,489.500328437498,2,0 +"83",25.25,226.982372632891,1,0,0,0 +"83",25.25,226.982372632891,1,2042.84135369602,1,0 +"83",25.25,226.982372632891,1,501.297020915714,2,0 +"83",25.25,226.982372632891,1,0,0,0 +"83",25.25,226.982372632891,1,2042.84135369602,1,0 +"83",25.25,226.982372632891,1,501.297020915714,2,0 +"83",25.5,294.136748366934,1,0,0,0 +"83",25.5,294.136748366934,1,2647.2307353024,1,0 +"83",25.5,294.136748366934,1,574.085757805151,2,0 +"83",25.7,315.229046025205,1,0,0,0 +"83",25.7,315.229046025205,1,2837.06141422684,1,0 +"83",25.7,315.229046025205,1,642.924013591852,2,0 +"83",26.3,490.473128770173,1,0,0,0 +"83",26.3,490.473128770173,1,4414.25815893156,1,0 +"83",26.3,490.473128770173,1,937.212895755892,2,0 +"83",26.5,474.753859502162,1,0,0,0 +"83",26.5,474.753859502162,1,4272.78473551946,1,0 +"83",26.5,474.753859502162,1,1044.91045763151,2,0 +"83",26.8,453.330017121509,1,0,0,0 +"83",26.8,453.330017121509,1,4079.97015409358,1,0 +"83",26.8,453.330017121509,1,1189.02217707739,2,0 +"83",30.1,323.839650058412,1,0,0,0 +"83",30.1,323.839650058412,1,2914.5568505257,1,0 +"83",30.1,323.839650058412,1,1919.81881169776,2,0 +"83",31,307.019025424495,1,0,0,0 +"83",31,307.019025424495,1,2763.17122882046,1,0 +"83",31,307.019025424495,1,1971.96003488716,2,0 +"83",31.4,300.808239620449,1,0,0,0 +"83",31.4,300.808239620449,1,2707.27415658404,1,0 +"83",31.4,300.808239620449,1,1985.31697367767,2,0 +"83",31.6,297.939709889991,1,0,0,0 +"83",31.6,297.939709889991,1,2681.45738900992,1,0 +"83",31.6,297.939709889991,1,1990.17842282757,2,0 +"83",31.6,297.939709889991,1,0,0,0 +"83",31.6,297.939709889991,1,2681.45738900992,1,0 +"83",31.6,297.939709889991,1,1990.17842282757,2,0 +"83",31.8,295.212771273936,1,0,0,0 +"83",31.8,295.212771273936,1,2656.91494146543,1,0 +"83",31.8,295.212771273936,1,1993.96132636636,2,0 +"83",33,281.265783475074,1,0,0,0 +"83",33,281.265783475074,1,2531.39205127567,1,0 +"83",33,281.265783475074,1,1998.55365154417,2,0 +"84",22.1,29.0165324646885,1,0,0,0 +"84",22.1,29.0165324646885,1,261.148792182197,1,0 +"84",22.1,29.0165324646885,1,223.839141968365,2,0 +"84",23.8,71.1229003341123,1,0,0,0 +"84",23.8,71.1229003341123,1,640.106103007011,1,0 +"84",23.8,71.1229003341123,1,304.900117939413,2,0 +"84",24.25,67.9598701858089,1,0,0,0 +"84",24.25,67.9598701858089,1,611.63883167228,1,0 +"84",24.25,67.9598701858089,1,322.421218710053,2,0 +"84",25.1,112.408366573233,1,0,0,0 +"84",25.1,112.408366573233,1,1011.6752991591,1,0 +"84",25.1,112.408366573233,1,391.974471893085,2,0 +"84",29.75,79.0862226675277,1,0,0,0 +"84",29.75,79.0862226675277,1,711.776004007749,1,0 +"84",29.75,79.0862226675277,1,542.538609646495,2,0 +"85",47,24.9235660742422,1,0,0,0 +"85",47,24.9235660742422,1,224.31209466818,1,0 +"85",47,24.9235660742422,1,192.303999273587,2,0 +"85",47.25,24.7934424245908,1,0,0,0 +"85",47.25,24.7934424245908,1,223.140981821317,1,0 +"85",47.25,24.7934424245908,1,191.299997965303,2,0 +"85",47.25,24.7934424245908,1,0,0,0 +"85",47.25,24.7934424245908,1,223.140981821317,1,0 +"85",47.25,24.7934424245908,1,191.299997965303,2,0 +"85",47.5,107.650209239254,1,0,0,0 +"85",47.5,107.650209239254,1,968.851883153286,1,0 +"85",47.5,107.650209239254,1,205.091693295852,2,0 +"85",48,307.366499772161,1,0,0,0 +"85",48,307.366499772161,1,2766.29849794945,1,0 +"85",48,307.366499772161,1,353.161817572817,2,0 +"85",48,307.366499772161,1,0,0,0 +"85",48,307.366499772161,1,2766.29849794945,1,0 +"85",48,307.366499772161,1,353.161817572817,2,0 +"85",48.3,433.349036609516,1,0,0,0 +"85",48.3,433.349036609516,1,3900.14132948565,1,0 +"85",48.3,433.349036609516,1,500.370208990582,2,0 +"85",48.5,491.864813357465,1,0,0,0 +"85",48.5,491.864813357465,1,4426.78332021718,1,0 +"85",48.5,491.864813357465,1,621.334634251983,2,0 +"85",48.75,470.45527761107,1,0,0,0 +"85",48.75,470.45527761107,1,4234.09749849963,1,0 +"85",48.75,470.45527761107,1,771.934203552969,2,0 +"85",48.8,466.418351751713,1,0,0,0 +"85",48.8,466.418351751713,1,4197.76516576541,1,0 +"85",48.8,466.418351751713,1,800.069006603337,2,0 +"85",49,488.021025972385,1,0,0,0 +"85",49,488.021025972385,1,4392.18923375147,1,0 +"85",49,488.021025972385,1,912.237437889903,2,0 +"85",49,488.021025972385,1,0,0,0 +"85",49,488.021025972385,1,4392.18923375147,1,0 +"85",49,488.021025972385,1,912.237437889903,2,0 +"85",49.1,498.688912678885,1,0,0,0 +"85",49.1,498.688912678885,1,4488.20021410996,1,0 +"85",49.1,498.688912678885,1,968.958781280105,2,0 +"85",49.25,514.532051046913,1,0,0,0 +"85",49.25,514.532051046913,1,4630.78845942221,1,0 +"85",49.25,514.532051046913,1,1054.77266847357,2,0 +"85",49.3,519.772150128497,1,0,0,0 +"85",49.3,519.772150128497,1,4677.94935115647,1,0 +"85",49.3,519.772150128497,1,1083.5615856884,2,0 +"85",49.5,503.547717877343,1,0,0,0 +"85",49.5,503.547717877343,1,4531.92946089609,1,0 +"85",49.5,503.547717877343,1,1193.77256352348,2,0 +"85",50,559.189683767056,1,0,0,0 +"85",50,559.189683767056,1,5032.7071539035,1,0 +"85",50,559.189683767056,1,1452.60939102258,2,0 +"85",50,559.189683767056,1,0,0,0 +"85",50,559.189683767056,1,5032.7071539035,1,0 +"85",50,559.189683767056,1,1452.60939102258,2,0 +"85",50.6,688.770175366817,1,0,0,0 +"85",50.6,688.770175366817,1,6198.93157830135,1,0 +"85",50.6,688.770175366817,1,1849.19583307033,2,0 +"85",51.2,634.369830348446,1,0,0,0 +"85",51.2,634.369830348446,1,5709.32847313601,1,0 +"85",51.2,634.369830348446,1,2200.08390470385,2,0 +"85",52,578.305559370799,1,0,0,0 +"85",52,578.305559370799,1,5204.75003433719,1,0 +"85",52,578.305559370799,1,2535.26842082181,2,0 +"85",52.25,563.847594554171,1,0,0,0 +"85",52.25,563.847594554171,1,5074.62835098754,1,0 +"85",52.25,563.847594554171,1,2615.4302186291,2,0 +"85",53.3,515.058492782474,1,0,0,0 +"85",53.3,515.058492782474,1,4635.52643504226,1,0 +"85",53.3,515.058492782474,1,2856.81255637152,2,0 +"85",54,490.696361733952,1,0,0,0 +"85",54,490.696361733952,1,4416.26725560557,1,0 +"85",54,490.696361733952,1,2952.97574425676,2,0 +"85",54.3,481.752509573146,1,0,0,0 +"85",54.3,481.752509573146,1,4335.77258615831,1,0 +"85",54.3,481.752509573146,1,2982.42381947084,2,0 +"85",54.5,476.212103322163,1,0,0,0 +"85",54.5,476.212103322163,1,4285.90892989947,1,0 +"85",54.5,476.212103322163,1,2998.76057819419,2,0 +"85",55.3,456.887327002093,1,0,0,0 +"85",55.3,456.887327002093,1,4111.98594301884,1,0 +"85",55.3,456.887327002093,1,3042.14713517952,2,0 +"85",55.7,448.634256259469,1,0,0,0 +"85",55.7,448.634256259469,1,4037.70830633522,1,0 +"85",55.7,448.634256259469,1,3053.04779169625,2,0 +"85",56,442.946159501024,1,0,0,0 +"85",56,442.946159501024,1,3986.51543550921,1,0 +"85",56,442.946159501024,1,3057.43618063993,2,0 +"85",56.5,434.27995694241,1,0,0,0 +"85",56.5,434.27995694241,1,3908.51961248169,1,0 +"85",56.5,434.27995694241,1,3058.68848886797,2,0 +"1",24.7,20.4606550099932,1,0,0,1 +"1",24.7,20.4606550099932,1,265.988515129912,1,1 +"1",24.7,20.4606550099932,1,271.278260157592,2,1 +"1",24.7,20.4606550099932,1,0,0,1 +"1",24.7,20.4606550099932,1,265.988515129912,1,1 +"1",24.7,20.4606550099932,1,271.278260157592,2,1 +"1",26.7,65.5968335513294,1,0,0,1 +"1",26.7,65.5968335513294,1,852.758836167282,1,1 +"1",26.7,65.5968335513294,1,327.851361702728,2,1 +"1",28.7,98.3023672769319,1,0,0,1 +"1",28.7,98.3023672769319,1,1277.93077460012,1,1 +"1",28.7,98.3023672769319,1,489.243891661985,2,1 +"1",31.7,73.9460255413392,1,0,0,1 +"1",31.7,73.9460255413392,1,961.29833203741,1,1 +"1",31.7,73.9460255413392,1,640.752253558926,2,1 +"1",33.3,66.3464143645202,1,0,0,1 +"1",33.3,66.3464143645202,1,862.503386738762,1,1 +"1",33.3,66.3464143645202,1,666.81506883902,2,1 +"11",26.6,13.9373874657271,1,0,0,1 +"11",26.6,13.9373874657271,1,181.186037054452,1,1 +"11",26.6,13.9373874657271,1,184.948657606794,2,1 +"11",27.25,50.2919212384422,1,0,0,1 +"11",27.25,50.2919212384422,1,653.794976099749,1,1 +"11",27.25,50.2919212384422,1,199.227577596986,2,1 +"11",32,32.2857770929119,1,0,0,1 +"11",32,32.2857770929119,1,419.715102207855,1,1 +"11",32,32.2857770929119,1,310.954425674665,2,1 +"12",26.7,13.9027032926659,1,0,0,1 +"12",26.7,13.9027032926659,1,180.735142804657,1,1 +"12",26.7,13.9027032926659,1,184.494749413106,2,1 +"12",28.75,47.2124961855609,1,0,0,1 +"12",28.75,47.2124961855609,1,613.762450412292,1,1 +"12",28.75,47.2124961855609,1,215.82111707548,2,1 +"12",34.25,29.737391375805,1,0,0,1 +"12",34.25,29.737391375805,1,386.586087885466,1,1 +"12",34.25,29.737391375805,1,311.839147568646,2,1 +"13",24.3,20.6668098557195,1,0,0,1 +"13",24.3,20.6668098557195,1,268.668528124353,1,1 +"13",24.3,20.6668098557195,1,273.944766452118,2,1 +"13",24.75,20.4350507457268,1,0,0,1 +"13",24.75,20.4350507457268,1,265.655659694449,1,1 +"13",24.75,20.4350507457268,1,270.946560472998,2,1 +"13",25,20.307571738421,1,0,0,1 +"13",25,20.307571738421,1,263.998432599473,1,1 +"13",25,20.307571738421,1,269.293461687099,2,1 +"13",27,89.7511144756614,1,0,0,1 +"13",27,89.7511144756614,1,1166.7644881836,1,1 +"13",27,89.7511144756614,1,349.115210772846,2,1 +"13",27.75,132.511886376457,1,0,0,1 +"13",27.75,132.511886376457,1,1722.65452289394,1,1 +"13",27.75,132.511886376457,1,438.917911795101,2,1 +"13",28.5,144.600166532856,1,0,0,1 +"13",28.5,144.600166532856,1,1879.80216492712,1,1 +"13",28.5,144.600166532856,1,559.860118874955,2,1 +"13",32.2,99.6234057732068,1,0,0,1 +"13",32.2,99.6234057732068,1,1295.10427505169,1,1 +"13",32.2,99.6234057732068,1,858.899992888144,2,1 +"13",32.75,95.685176465808,1,0,0,1 +"13",32.75,95.685176465808,1,1243.9072940555,1,1 +"13",32.75,95.685176465808,1,875.199579235402,2,1 +"13",34.5,85.7659885119325,1,0,0,1 +"13",34.5,85.7659885119325,1,1114.95785065512,1,1 +"13",34.5,85.7659885119325,1,901.277373186497,2,1 +"14",21.9,15.6832110468935,1,0,0,1 +"14",21.9,15.6832110468935,1,203.881743609615,1,1 +"14",21.9,15.6832110468935,1,207.437713025469,2,1 +"14",22.9,49.8379799806714,1,0,0,1 +"14",22.9,49.8379799806714,1,647.893739748728,1,1 +"14",22.9,49.8379799806714,1,238.001874572803,2,1 +"14",24.2,77.273035038277,1,0,0,1 +"14",24.2,77.273035038277,1,1004.5494554976,1,1 +"14",24.2,77.273035038277,1,324.571012656264,2,1 +"14",24.25,76.792679388069,1,0,0,1 +"14",24.25,76.792679388069,1,998.304832044897,1,1 +"14",24.25,76.792679388069,1,328.31208500518,2,1 +"14",28.1,53.0617788219428,1,0,0,1 +"14",28.1,53.0617788219428,1,689.803124685256,1,1 +"14",28.1,53.0617788219428,1,478.845711710116,2,1 +"14",29.75,47.7352470082216,1,0,0,1 +"14",29.75,47.7352470082216,1,620.558211106881,1,1 +"14",29.75,47.7352470082216,1,494.197392630562,2,1 +"15",23.5,18.0740235376631,1,0,0,1 +"15",23.5,18.0740235376631,1,234.962305989621,1,1 +"15",23.5,18.0740235376631,1,239.439430799386,2,1 +"15",24.5,58.4268484282804,1,0,0,1 +"15",24.5,58.4268484282804,1,759.549029567645,1,1 +"15",24.5,58.4268484282804,1,282.148302801959,2,1 +"15",29.75,37.739079307957,1,0,0,1 +"15",29.75,37.739079307957,1,490.608031003441,1,1 +"15",29.75,37.739079307957,1,393.89157677464,2,1 +"16",19.7,16.5931630683838,1,0,0,1 +"16",19.7,16.5931630683838,1,215.711119888989,1,1 +"16",19.7,16.5931630683838,1,218.677598821196,2,1 +"16",21,48.7457696623765,1,0,0,1 +"16",21,48.7457696623765,1,633.695005610895,1,1 +"16",21,48.7457696623765,1,263.901313154913,2,1 +"16",23.9,37.3919273096984,1,0,0,1 +"16",23.9,37.3919273096984,1,486.09505502608,1,1 +"16",23.9,37.3919273096984,1,331.53011989532,2,1 +"16",25.25,66.8590891604432,1,0,0,1 +"16",25.25,66.8590891604432,1,869.168159085762,1,1 +"16",25.25,66.8590891604432,1,394.125939456601,2,1 +"16",26,61.9853883868673,1,0,0,1 +"16",26,61.9853883868673,1,805.810049029274,1,1 +"16",26,61.9853883868673,1,426.114421534915,2,1 +"16",30.25,45.7320514681289,1,0,0,1 +"16",30.25,45.7320514681289,1,594.516669085676,1,1 +"16",30.25,45.7320514681289,1,491.981290253959,2,1 +"17",24.5,14.6881695545288,1,0,0,1 +"17",24.5,14.6881695545288,1,190.946204208875,1,1 +"17",24.5,14.6881695545288,1,194.720463605836,2,1 +"17",25.75,47.9694095566992,1,0,0,1 +"17",25.75,47.9694095566992,1,623.602324237089,1,1 +"17",25.75,47.9694095566992,1,231.628730198853,2,1 +"17",31.3,30.4755870614061,1,0,0,1 +"17",31.3,30.4755870614061,1,396.182631798279,1,1 +"17",31.3,30.4755870614061,1,323.995837442073,2,1 +"18",24.75,14.5964648183763,1,0,0,1 +"18",24.75,14.5964648183763,1,189.754042638892,1,1 +"18",24.75,14.5964648183763,1,193.533257480713,2,1 +"18",26.75,45.3664851877912,1,0,0,1 +"18",26.75,45.3664851877912,1,589.764307441286,1,1 +"18",26.75,45.3664851877912,1,246.796812741881,2,1 +"18",28.75,37.3695131025387,1,0,0,1 +"18",28.75,37.3695131025387,1,485.803670333004,1,1 +"18",28.75,37.3695131025387,1,297.395110714032,2,1 +"18",30.3,67.2748640255676,1,0,0,1 +"18",30.3,67.2748640255676,1,874.573232332379,1,1 +"18",30.3,67.2748640255676,1,355.569921822257,2,1 +"18",31,62.3862900108588,1,0,0,1 +"18",31,62.3862900108588,1,811.021770141165,1,1 +"18",31,62.3862900108588,1,389.655702800517,2,1 +"18",33.3,51.0462771939179,1,0,0,1 +"18",33.3,51.0462771939179,1,663.601603520933,1,1 +"18",33.3,51.0462771939179,1,453.037633608152,2,1 +"19",24,14.873565760885,1,0,0,1 +"19",24,14.873565760885,1,193.356354891505,1,1 +"19",24,14.873565760885,1,197.114022383359,2,1 +"19",25.3,46.8455942149885,1,0,0,1 +"19",25.3,46.8455942149885,1,608.99272479485,1,1 +"19",25.3,46.8455942149885,1,245.049742374357,2,1 +"19",27.8,36.8771945071954,1,0,0,1 +"19",27.8,36.8771945071954,1,479.40352859354,1,1 +"19",27.8,36.8771945071954,1,307.419448534918,2,1 +"2",23.8,20.9278744261593,1,0,0,1 +"2",23.8,20.9278744261593,1,272.062367540071,1,1 +"2",23.8,20.9278744261593,1,277.309960311524,2,1 +"2",24.7,68.8261924999787,1,0,0,1 +"2",24.7,68.8261924999787,1,894.740502499724,1,1 +"2",24.7,68.8261924999787,1,321.025300051276,2,1 +"2",28.75,47.5521775873266,1,0,0,1 +"2",28.75,47.5521775873266,1,618.178308635246,1,1 +"2",28.75,47.5521775873266,1,448.802759087863,2,1 +"20",25.8,24.1710640322576,1,0,0,1 +"20",25.8,24.1710640322576,1,314.223832419348,1,1 +"20",25.8,24.1710640322576,1,320.64949976764,2,1 +"20",31.4,59.8659388146705,1,0,0,1 +"20",31.4,59.8659388146705,1,778.257204590716,1,1 +"20",31.4,59.8659388146705,1,502.761600252845,2,1 +"21",20.75,11.3050845345579,1,0,0,1 +"21",20.75,11.3050845345579,1,146.966098949253,1,1 +"21",20.75,11.3050845345579,1,149.285128614906,2,1 +"21",21.25,36.9806629966368,1,0,0,1 +"21",21.25,36.9806629966368,1,480.748618956279,1,1 +"21",21.25,36.9806629966368,1,157.597795290789,2,1 +"21",22.25,32.9179726152287,1,0,0,1 +"21",22.25,32.9179726152287,1,427.933643997973,1,1 +"21",22.25,32.9179726152287,1,187.751432095903,2,1 +"21",22.25,32.9179726152287,1,0,0,1 +"21",22.25,32.9179726152287,1,427.933643997973,1,1 +"21",22.25,32.9179726152287,1,187.751432095903,2,1 +"21",23.5,75.9387991021575,1,0,0,1 +"21",23.5,75.9387991021575,1,987.204388328047,1,1 +"21",23.5,75.9387991021575,1,275.710635611673,2,1 +"21",23.5,75.9387991021575,1,0,0,1 +"21",23.5,75.9387991021575,1,987.204388328047,1,1 +"21",23.5,75.9387991021575,1,275.710635611673,2,1 +"21",24,71.2587257582603,1,0,0,1 +"21",24,71.2587257582603,1,926.363434857385,1,1 +"21",24,71.2587257582603,1,312.6483836877,2,1 +"21",25.25,85.6645196012682,1,0,0,1 +"21",25.25,85.6645196012682,1,1113.63875481649,1,1 +"21",25.25,85.6645196012682,1,409.181890535243,2,1 +"21",25.9,79.5665118293085,1,0,0,1 +"21",25.9,79.5665118293085,1,1034.36465378101,1,1 +"21",25.9,79.5665118293085,1,453.586082766698,2,1 +"21",27.3,94.720449916804,1,0,0,1 +"21",27.3,94.720449916804,1,1231.36584891845,1,1 +"21",27.3,94.720449916804,1,533.182610291982,2,1 +"21",27.8,89.8500335145702,1,0,0,1 +"21",27.8,89.8500335145702,1,1168.05043568941,1,1 +"21",27.8,89.8500335145702,1,566.521616122745,2,1 +"21",29,80.3092490430036,1,0,0,1 +"21",29,80.3092490430036,1,1044.02023755905,1,1 +"21",29,80.3092490430036,1,624.366850679038,2,1 +"21",29,80.3092490430036,1,0,0,1 +"21",29,80.3092490430036,1,1044.02023755905,1,1 +"21",29,80.3092490430036,1,624.366850679038,2,1 +"21",29.75,75.5684061232696,1,0,0,1 +"21",29.75,75.5684061232696,1,982.389279602505,1,1 +"21",29.75,75.5684061232696,1,648.035351250544,2,1 +"21",32,65.1766265137191,1,0,0,1 +"21",32,65.1766265137191,1,847.296144678349,1,1 +"21",32,65.1766265137191,1,680.792762259129,2,1 +"22",24.5,20.5634373763403,1,0,0,1 +"22",24.5,20.5634373763403,1,267.324685892424,1,1 +"22",24.5,20.5634373763403,1,272.60864904817,2,1 +"22",24.8,51.9081096045429,1,0,0,1 +"22",24.8,51.9081096045429,1,674.805424859058,1,1 +"22",24.8,51.9081096045429,1,278.03601767006,2,1 +"22",25.5,92.6353114681598,1,0,0,1 +"22",25.5,92.6353114681598,1,1204.25904908608,1,1 +"22",25.5,92.6353114681598,1,340.298745505253,2,1 +"22",26,86.9465156294487,1,0,0,1 +"22",26,86.9465156294487,1,1130.30470318283,1,1 +"22",26,86.9465156294487,1,385.090954050996,2,1 +"22",31,56.0291799779912,1,0,0,1 +"22",31,56.0291799779912,1,728.379339713886,1,1 +"22",31,56.0291799779912,1,564.219789315323,2,1 +"22",31,56.0291799779912,1,0,0,1 +"22",31,56.0291799779912,1,728.379339713886,1,1 +"22",31,56.0291799779912,1,564.219789315323,2,1 +"22",47.5,32.3363656364649,1,0,0,1 +"22",47.5,32.3363656364649,1,420.372753274044,1,1 +"22",47.5,32.3363656364649,1,428.171241003772,2,1 +"22",49.25,53.8339159658958,1,0,0,1 +"22",49.25,53.8339159658958,1,699.840907556646,1,1 +"22",49.25,53.8339159658958,1,446.616107086692,2,1 +"22",55,39.1102734587371,1,0,0,1 +"22",55,39.1102734587371,1,508.433554963583,1,1 +"22",55,39.1102734587371,1,469.096243509433,2,1 +"23",21.6,11.0623020014804,1,0,0,1 +"23",21.6,11.0623020014804,1,143.809926019245,1,1 +"23",21.6,11.0623020014804,1,146.262633827129,2,1 +"23",22.3,35.8462461868212,1,0,0,1 +"23",22.3,35.8462461868212,1,466.001200428675,1,1 +"23",22.3,35.8462461868212,1,161.527060226039,2,1 +"23",23.75,30.5460665897562,1,0,0,1 +"23",23.75,30.5460665897562,1,397.09886566683,1,1 +"23",23.75,30.5460665897562,1,199.292643352201,2,1 +"23",27.3,23.1742817358326,1,0,0,1 +"23",27.3,23.1742817358326,1,301.265662565824,1,1 +"23",27.3,23.1742817358326,1,234.253968651316,2,1 +"24",24.25,20.6927460811763,1,0,0,1 +"24",24.25,20.6927460811763,1,269.005699055292,1,1 +"24",24.25,20.6927460811763,1,274.279688203791,2,1 +"24",26.25,56.2793304580266,1,0,0,1 +"24",26.25,56.2793304580266,1,731.631295954346,1,1 +"24",26.25,56.2793304580266,1,271.212047606085,2,1 +"24",31.8,43.8731728043827,1,0,0,1 +"24",31.8,43.8731728043827,1,570.351246456975,1,1 +"24",31.8,43.8731728043827,1,444.11848726462,2,1 +"26",23,15.2526303348927,1,0,0,1 +"26",23,15.2526303348927,1,198.284194353605,1,1 +"26",23,15.2526303348927,1,201.976584845673,2,1 +"26",23.75,51.5456059265703,1,0,0,1 +"26",23.75,51.5456059265703,1,670.092877045414,1,1 +"26",23.75,51.5456059265703,1,215.531051456413,2,1 +"26",24.2,48.7999563703827,1,0,0,1 +"26",24.2,48.7999563703827,1,634.399432814975,1,1 +"26",24.2,48.7999563703827,1,236.55666930537,2,1 +"26",24.25,48.5139094616257,1,0,0,1 +"26",24.25,48.5139094616257,1,630.680823001135,1,1 +"26",24.25,48.5139094616257,1,238.693938641846,2,1 +"26",25.4,95.9686504588327,1,0,0,1 +"26",25.4,95.9686504588327,1,1247.59245596483,1,1 +"26",25.4,95.9686504588327,1,354.363235896095,2,1 +"26",25.75,110.598305766885,1,0,0,1 +"26",25.75,110.598305766885,1,1437.7779749695,1,1 +"26",25.75,110.598305766885,1,391.28904246539,2,1 +"26",26.5,117.950644344769,1,0,0,1 +"26",26.5,117.950644344769,1,1533.35837648199,1,1 +"26",26.5,117.950644344769,1,486.89242482257,2,1 +"26",27,110.981078825805,1,0,0,1 +"26",27,110.981078825805,1,1442.75402473547,1,1 +"26",27,110.981078825805,1,540.319546713045,2,1 +"26",28.3,180.568633479435,1,0,0,1 +"26",28.3,180.568633479435,1,2347.39223523265,1,1 +"26",28.3,180.568633479435,1,715.787129746905,2,1 +"26",30,148.739472241507,1,0,0,1 +"26",30,148.739472241507,1,1933.61313913959,1,1 +"26",30,148.739472241507,1,948.876348314949,2,1 +"26",30.7,139.060048214152,1,0,0,1 +"26",30.7,139.060048214152,1,1807.78062678397,1,1 +"26",30.7,139.060048214152,1,1009.29621625108,2,1 +"26",30.75,138.428662335267,1,0,0,1 +"26",30.75,138.428662335267,1,1799.57261035847,1,1 +"26",30.75,138.428662335267,1,1012.99506133091,2,1 +"26",32,124.799941270289,1,0,0,1 +"26",32,124.799941270289,1,1622.39923651375,1,1 +"26",32,124.799941270289,1,1083.49504339177,2,1 +"26",32.4,121.185482217131,1,0,0,1 +"26",32.4,121.185482217131,1,1575.41126882271,1,1 +"26",32.4,121.185482217131,1,1098.5117160011,2,1 +"26",35,103.685779960099,1,0,0,1 +"26",35,103.685779960099,1,1347.91513948129,1,1 +"26",35,103.685779960099,1,1137.23623198623,2,1 +"27",24.5,20.5634373763403,1,0,0,1 +"27",24.5,20.5634373763403,1,267.324685892424,1,1 +"27",24.5,20.5634373763403,1,272.60864904817,2,1 +"27",25.5,67.6565013819978,1,0,0,1 +"27",25.5,67.6565013819978,1,879.534517965971,1,1 +"27",25.5,67.6565013819978,1,322.578727456021,2,1 +"27",30,45.5748076841007,1,0,0,1 +"27",30,45.5748076841007,1,592.47249989331,1,1 +"27",30,45.5748076841007,1,449.581547648489,2,1 +"28",25,20.307571738421,1,0,0,1 +"28",25,20.307571738421,1,263.998432599473,1,1 +"28",25,20.307571738421,1,269.293461687099,2,1 +"28",26.25,66.9100590971779,1,0,0,1 +"28",26.25,66.9100590971779,1,869.830768263313,1,1 +"28",26.25,66.9100590971779,1,321.055560542721,2,1 +"28",30.9,44.7089316410467,1,0,0,1 +"28",30.9,44.7089316410467,1,581.216111333608,1,1 +"28",30.9,44.7089316410467,1,446.901479769065,2,1 +"29",24.25,14.7805329151259,1,0,0,1 +"29",24.25,14.7805329151259,1,192.146927896637,1,1 +"29",24.25,14.7805329151259,1,195.914063002708,2,1 +"29",25.5,47.2773794287676,1,0,0,1 +"29",25.5,47.2773794287676,1,614.605932573979,1,1 +"29",25.5,47.2773794287676,1,239.695049352109,2,1 +"29",25.8,45.699361956148,1,0,0,1 +"29",25.8,45.699361956148,1,594.091705429924,1,1 +"29",25.8,45.699361956148,1,251.145980375525,2,1 +"29",27.6,69.3426505338017,1,0,0,1 +"29",27.6,69.3426505338017,1,901.454456939422,1,1 +"29",27.6,69.3426505338017,1,364.691647922901,2,1 +"29",31.5,49.4051596338389,1,0,0,1 +"29",31.5,49.4051596338389,1,642.267075239905,1,1 +"29",31.5,49.4051596338389,1,477.129455500406,2,1 +"29",32.6,46.2402187388816,1,0,0,1 +"29",32.6,46.2402187388816,1,601.122843605461,1,1 +"29",32.6,46.2402187388816,1,484.121044700241,2,1 +"3",22,21.9008228314911,1,0,0,1 +"3",22,21.9008228314911,1,284.710696809384,1,1 +"3",22,21.9008228314911,1,289.711180608914,2,1 +"3",23.6,66.1159668823679,1,0,0,1 +"3",23.6,66.1159668823679,1,859.507569470782,1,1 +"3",23.6,66.1159668823679,1,357.434162787303,2,1 +"3",26.1,52.2666891169204,1,0,0,1 +"3",26.1,52.2666891169204,1,679.466958519966,1,1 +"3",26.1,52.2666891169204,1,442.401410196463,2,1 +"3",27,97.3014473609654,1,0,0,1 +"3",27,97.3014473609654,1,1264.91881569255,1,1 +"3",27,97.3014473609654,1,505.878509374285,2,1 +"3",29.2,78.4308207379332,1,0,0,1 +"3",29.2,78.4308207379332,1,1019.60066959313,1,1 +"3",29.2,78.4308207379332,1,626.738888874054,2,1 +"3",32.7,62.3376401535703,1,0,0,1 +"3",32.7,62.3376401535703,1,810.389321996414,1,1 +"3",32.7,62.3376401535703,1,677.997965574737,2,1 +"30",24.2,14.7990855680112,1,0,0,1 +"30",24.2,14.7990855680112,1,192.388112384145,1,1 +"30",24.2,14.7990855680112,1,196.153547242907,2,1 +"30",24.2,14.7990855680112,1,0,0,1 +"30",24.2,14.7990855680112,1,192.388112384145,1,1 +"30",24.2,14.7990855680112,1,196.153547242907,2,1 +"30",25.25,82.1492377822273,1,0,0,1 +"30",25.25,82.1492377822273,1,1067.94009116895,1,1 +"30",25.25,82.1492377822273,1,274.361129878961,2,1 +"30",25.4,80.5223010193469,1,0,0,1 +"30",25.4,80.5223010193469,1,1046.78991325151,1,1 +"30",25.4,80.5223010193469,1,287.581588420566,2,1 +"30",26.8,68.0205789045355,1,0,0,1 +"30",26.8,68.0205789045355,1,884.267525758961,1,1 +"30",26.8,68.0205789045355,1,382.855481849584,2,1 +"30",30.75,71.8907585661378,1,0,0,1 +"30",30.75,71.8907585661378,1,934.579861359791,1,1 +"30",30.75,71.8907585661378,1,615.200189901764,2,1 +"30",31,70.544569908,1,0,0,1 +"30",31,70.544569908,1,917.079408804,1,1 +"30",31,70.544569908,1,621.128775465013,2,1 +"30",31.2,69.5193749859809,1,0,0,1 +"30",31.2,69.5193749859809,1,903.751874817752,1,1 +"30",31.2,69.5193749859809,1,625.352635154306,2,1 +"30",33.3,60.9642396267447,1,0,0,1 +"30",33.3,60.9642396267447,1,792.535115147681,1,1 +"30",33.3,60.9642396267447,1,647.922036541079,2,1 +"31",23.5,15.0616862813859,1,0,0,1 +"31",23.5,15.0616862813859,1,195.801921658017,1,1 +"31",23.5,15.0616862813859,1,199.532858999489,2,1 +"31",25,49.0522208774333,1,0,0,1 +"31",25,49.0522208774333,1,637.678871406632,1,1 +"31",25,49.0522208774333,1,227.886072557702,2,1 +"31",25.4,69.3086729677574,1,0,0,1 +"31",25.4,69.3086729677574,1,901.012748580847,1,1 +"31",25.4,69.3086729677574,1,249.882901846459,2,1 +"31",26.1,77.3894931775731,1,0,0,1 +"31",26.1,77.3894931775731,1,1006.06341130845,1,1 +"31",26.1,77.3894931775731,1,308.961346719212,2,1 +"31",29.4,55.2283415608579,1,0,0,1 +"31",29.4,55.2283415608579,1,717.968440291153,1,1 +"31",29.4,55.2283415608579,1,457.99004142371,2,1 +"31",30,52.7495287419023,1,0,0,1 +"31",30,52.7495287419023,1,685.74387364473,1,1 +"31",30,52.7495287419023,1,469.169618424503,2,1 +"32",24,25.2850617935044,1,0,0,1 +"32",24,25.2850617935044,1,328.705803315558,1,1 +"32",24,25.2850617935044,1,335.09383805171,2,1 +"32",26.25,71.9750976496199,1,0,0,1 +"32",26.25,71.9750976496199,1,935.676269445058,1,1 +"32",26.25,71.9750976496199,1,469.478240890151,2,1 +"32",26.5,70.2383726107969,1,0,0,1 +"32",26.5,70.2383726107969,1,913.098843940359,1,1 +"32",26.5,70.2383726107969,1,480.502242736685,2,1 +"32",27.3,65.3277986284375,1,0,0,1 +"32",27.3,65.3277986284375,1,849.261382169687,1,1 +"32",27.3,65.3277986284375,1,509.13263251045,2,1 +"32",28.75,112.344117150808,1,0,0,1 +"32",28.75,112.344117150808,1,1460.4735229605,1,1 +"32",28.75,112.344117150808,1,643.553558055472,2,1 +"32",30.5,95.0114231545532,1,0,0,1 +"32",30.5,95.0114231545532,1,1235.14850100919,1,1 +"32",30.5,95.0114231545532,1,751.633524678019,2,1 +"32",34.25,74.3853799100726,1,0,0,1 +"32",34.25,74.3853799100726,1,967.009938830944,1,1 +"32",34.25,74.3853799100726,1,816.500776303545,2,1 +"33",24.6,20.5119729007666,1,0,0,1 +"33",24.6,20.5119729007666,1,266.655647709966,1,1 +"33",24.6,20.5119729007666,1,271.942737193146,2,1 +"33",26,65.2345227829366,1,0,0,1 +"33",26,65.2345227829366,1,848.048796178176,1,1 +"33",26,65.2345227829366,1,338.519503739928,2,1 +"33",31,43.3774147668463,1,0,0,1 +"33",31,43.3774147668463,1,563.906391969002,1,1 +"33",31,43.3774147668463,1,452.732039110842,2,1 +"34",24,22.3103486413274,1,0,0,1 +"34",24,22.3103486413274,1,290.034532337257,1,1 +"34",24,22.3103486413274,1,295.671033575038,2,1 +"34",25.7,72.3820324947189,1,0,0,1 +"34",25.7,72.3820324947189,1,940.966422431345,1,1 +"34",25.7,72.3820324947189,1,344.225606854376,2,1 +"34",31.7,44.7594008682905,1,0,0,1 +"34",31.7,44.7594008682905,1,581.872211287777,1,1 +"34",31.7,44.7594008682905,1,486.635510371963,2,1 +"36",24.3,14.7620070397996,1,0,0,1 +"36",24.3,14.7620070397996,1,191.906091517395,1,1 +"36",24.3,14.7620070397996,1,195.674833180084,2,1 +"37",20.2,16.3800698759474,1,0,0,1 +"37",20.2,16.3800698759474,1,212.940908387316,1,1 +"37",20.2,16.3800698759474,1,216.089767758552,2,1 +"37",20.9,52.0662594894186,1,0,0,1 +"37",20.9,52.0662594894186,1,676.861373362442,1,1 +"37",20.9,52.0662594894186,1,235.154048494635,2,1 +"37",21.3,49.6579377599702,1,0,0,1 +"37",21.3,49.6579377599702,1,645.553190879612,1,1 +"37",21.3,49.6579377599702,1,253.243396719657,2,1 +"37",22.2,79.595049129006,1,0,0,1 +"37",22.2,79.595049129006,1,1034.73563867708,1,1 +"37",22.2,79.595049129006,1,321.051838614452,2,1 +"37",24.5,61.963946237377,1,0,0,1 +"37",24.5,61.963946237377,1,805.5313010859,1,1 +"37",24.5,61.963946237377,1,445.692917041627,2,1 +"37",25.3,57.8020222241992,1,0,0,1 +"37",25.3,57.8020222241992,1,751.426288914589,1,1 +"37",25.3,57.8020222241992,1,468.692306984939,2,1 +"38",24,14.873565760885,1,0,0,1 +"38",24,14.873565760885,1,193.356354891505,1,1 +"38",24,14.873565760885,1,197.114022383359,2,1 +"38",24.2,29.9245354589145,1,0,0,1 +"38",24.2,29.9245354589145,1,389.018960965889,1,1 +"38",24.2,29.9245354589145,1,198.533980071222,2,1 +"38",24.3,44.8879543034966,1,0,0,1 +"38",24.3,44.8879543034966,1,583.543405945455,1,1 +"38",24.3,44.8879543034966,1,201.576757051955,2,1 +"38",24.5,74.071815256643,1,0,0,1 +"38",24.5,74.071815256643,1,962.933598336359,1,1 +"38",24.5,74.071815256643,1,214.443641548725,2,1 +"38",25,128.61203183314,1,0,0,1 +"38",25,128.61203183314,1,1671.95641383082,1,1 +"38",25,128.61203183314,1,273.86229958826,2,1 +"38",25.1,141.98742408748,1,0,0,1 +"38",25.1,141.98742408748,1,1845.83651313724,1,1 +"38",25.1,141.98742408748,1,291.186578865506,2,1 +"38",25.7,144.300555556226,1,0,0,1 +"38",25.7,144.300555556226,1,1875.90722223093,1,1 +"38",25.7,144.300555556226,1,402.835080484455,2,1 +"38",25.7,144.300555556226,1,0,0,1 +"38",25.7,144.300555556226,1,1875.90722223093,1,1 +"38",25.7,144.300555556226,1,402.835080484455,2,1 +"38",30,91.1837620212987,1,0,0,1 +"38",30,91.1837620212987,1,1185.38890627688,1,1 +"38",30,91.1837620212987,1,777.204899340885,2,1 +"38",30.3,89.1361213074646,1,0,0,1 +"38",30.3,89.1361213074646,1,1158.76957699704,1,1 +"38",30.3,89.1361213074646,1,786.245239011223,2,1 +"38",30.5,87.8437885224583,1,0,0,1 +"38",30.5,87.8437885224583,1,1141.96925079196,1,1 +"38",30.5,87.8437885224583,1,791.542478174002,2,1 +"38",31.2,83.7305760393065,1,0,0,1 +"38",31.2,83.7305760393065,1,1088.49748851098,1,1 +"38",31.2,83.7305760393065,1,806.004025336757,2,1 +"39",24.75,20.4350507457268,1,0,0,1 +"39",24.75,20.4350507457268,1,265.655659694449,1,1 +"39",24.75,20.4350507457268,1,270.946560472998,2,1 +"39",26.2,65.8409407277749,1,0,0,1 +"39",26.2,65.8409407277749,1,855.932229461073,1,1 +"39",26.2,65.8409407277749,1,331.043276363956,2,1 +"39",30.5,45.3538349606779,1,0,0,1 +"39",30.5,45.3538349606779,1,589.599854488813,1,1 +"39",30.5,45.3538349606779,1,446.662519599328,2,1 +"4",14.5,26.7736798398201,1,0,0,1 +"4",14.5,26.7736798398201,1,348.057837917661,1,1 +"4",14.5,26.7736798398201,1,344.326627485711,2,1 +"4",18.1,58.849947950812,1,0,0,1 +"4",18.1,58.849947950812,1,765.049323360556,1,1 +"4",18.1,58.849947950812,1,482.656418059831,2,1 +"4",19.8,52.0900868719974,1,0,0,1 +"4",19.8,52.0900868719974,1,677.171129335966,1,1 +"4",19.8,52.0900868719974,1,509.467246389697,2,1 +"40",25.25,20.1809866066984,1,0,0,1 +"40",25.25,20.1809866066984,1,262.352825887079,1,1 +"40",25.25,20.1809866066984,1,267.649385048303,2,1 +"40",26.4,67.2110713392049,1,0,0,1 +"40",26.4,67.2110713392049,1,873.743927409664,1,1 +"40",26.4,67.2110713392049,1,316.767801549156,2,1 +"40",26.75,64.5221460768872,1,0,0,1 +"40",26.75,64.5221460768872,1,838.787898999534,1,1 +"40",26.75,64.5221460768872,1,336.743700843572,2,1 +"40",28.1,101.521179922741,1,0,0,1 +"40",28.1,101.521179922741,1,1319.77533899563,1,1 +"40",28.1,101.521179922741,1,469.353271284966,2,1 +"40",29.9,83.8027452205931,1,0,0,1 +"40",29.9,83.8027452205931,1,1089.43568786771,1,1 +"40",29.9,83.8027452205931,1,592.04631674912,2,1 +"40",32.75,67.3684984590824,1,0,0,1 +"40",32.75,67.3684984590824,1,875.790479968072,1,1 +"40",32.75,67.3684984590824,1,667.257561786837,2,1 +"41",23,10.6768412344249,1,0,0,1 +"41",23,10.6768412344249,1,138.798936047523,1,1 +"41",23,10.6768412344249,1,141.383609391971,2,1 +"41",24.7,34.0309370666567,1,0,0,1 +"41",24.7,34.0309370666567,1,442.402181866537,1,1 +"41",24.7,34.0309370666567,1,163.92131552252,2,1 +"41",29,23.2589484271522,1,0,0,1 +"41",29,23.2589484271522,1,302.366329552978,1,1 +"41",29,23.2589484271522,1,226.373965951943,2,1 +"42",24.3,14.7620070397996,1,0,0,1 +"42",24.3,14.7620070397996,1,191.906091517395,1,1 +"42",24.3,14.7620070397996,1,195.674833180084,2,1 +"42",25.25,48.6869889492649,1,0,0,1 +"42",25.25,48.6869889492649,1,632.930856340443,1,1 +"42",25.25,48.6869889492649,1,229.166018178517,2,1 +"42",27,40.4390061200357,1,0,0,1 +"42",27,40.4390061200357,1,525.707079560464,1,1 +"42",27,40.4390061200357,1,286.036896578111,2,1 +"42",28.3,69.673751981544,1,0,0,1 +"42",28.3,69.673751981544,1,905.758775760071,1,1 +"42",28.3,69.673751981544,1,353.914452396647,2,1 +"42",32,50.000842181419,1,0,0,1 +"42",32,50.000842181419,1,650.010948358448,1,1 +"42",32,50.000842181419,1,469.136881079233,2,1 +"43",24.4,14.7250352681066,1,0,0,1 +"43",24.4,14.7250352681066,1,191.425458485386,1,1 +"43",24.4,14.7250352681066,1,195.197137913445,2,1 +"43",25.7,47.2059953428675,1,0,0,1 +"43",25.7,47.2059953428675,1,613.677939457277,1,1 +"43",25.7,47.2059953428675,1,238.765410500725,2,1 +"43",31,30.7036768959263,1,0,0,1 +"43",31,30.7036768959263,1,399.147799647042,1,1 +"43",31,30.7036768959263,1,324.637219712892,2,1 +"44",24.5,20.5634373763403,1,0,0,1 +"44",24.5,20.5634373763403,1,267.324685892424,1,1 +"44",24.5,20.5634373763403,1,272.60864904817,2,1 +"44",25.6,51.5038711441644,1,0,0,1 +"44",25.6,51.5038711441644,1,669.550324874137,1,1 +"44",25.6,51.5038711441644,1,272.783680627069,2,1 +"44",26.5,65.6940803800861,1,0,0,1 +"44",26.5,65.6940803800861,1,854.023044941119,1,1 +"44",26.5,65.6940803800861,1,329.123760289105,2,1 +"44",29.2,90.4845263498764,1,0,0,1 +"44",29.2,90.4845263498764,1,1176.29884254839,1,1 +"44",29.2,90.4845263498764,1,547.632937853167,2,1 +"44",31.8,72.1159917537955,1,0,0,1 +"44",31.8,72.1159917537955,1,937.507892799342,1,1 +"44",31.8,72.1159917537955,1,650.582323406806,2,1 +"44",32.75,67.6540513877603,1,0,0,1 +"44",32.75,67.6540513877603,1,879.502668040884,1,1 +"44",32.75,67.6540513877603,1,665.47895463386,2,1 +"45",24.5,20.5634373763403,1,0,0,1 +"45",24.5,20.5634373763403,1,267.324685892424,1,1 +"45",24.5,20.5634373763403,1,272.60864904817,2,1 +"45",25.8,67.5068012675322,1,0,0,1 +"45",25.8,67.5068012675322,1,877.588416477919,1,1 +"45",25.8,67.5068012675322,1,320.62866481374,2,1 +"45",30.7,44.347098480988,1,0,0,1 +"45",30.7,44.347098480988,1,576.512280252845,1,1 +"45",30.7,44.347098480988,1,450.387453597948,2,1 +"46",23.6,25.5405273612775,1,0,0,1 +"46",23.6,25.5405273612775,1,332.026855696608,1,1 +"46",23.6,25.5405273612775,1,338.38004715069,2,1 +"46",24,57.2920525140996,1,0,0,1 +"46",24,57.2920525140996,1,744.796682683294,1,1 +"46",24,57.2920525140996,1,341.38404008779,2,1 +"46",24.25,119.837761802276,1,0,0,1 +"46",24.25,119.837761802276,1,1557.89090342959,1,1 +"46",24.25,119.837761802276,1,363.862543532236,2,1 +"46",25.25,191.412612852417,1,0,0,1 +"46",25.25,191.412612852417,1,2488.36396708142,1,1 +"46",25.25,191.412612852417,1,588.239122818108,2,1 +"46",25.25,191.412612852417,1,0,0,1 +"46",25.25,191.412612852417,1,2488.36396708142,1,1 +"46",25.25,191.412612852417,1,588.239122818108,2,1 +"46",25.25,191.412612852417,1,0,0,1 +"46",25.25,191.412612852417,1,2488.36396708142,1,1 +"46",25.25,191.412612852417,1,588.239122818108,2,1 +"46",26.2,225.918109468386,1,0,0,1 +"46",26.2,225.918109468386,1,2936.93542308902,1,1 +"46",26.2,225.918109468386,1,785.201785403574,2,1 +"46",26.6,265.06875773731,1,0,0,1 +"46",26.6,265.06875773731,1,3445.89385058503,1,1 +"46",26.6,265.06875773731,1,892.367132831137,2,1 +"46",27.5,253.618441598733,1,0,0,1 +"46",27.5,253.618441598733,1,3297.03974078353,1,1 +"46",27.5,253.618441598733,1,1140.01396801276,2,1 +"46",27.75,246.132797865926,1,0,0,1 +"46",27.75,246.132797865926,1,3199.72637225704,1,1 +"46",27.75,246.132797865926,1,1196.72901634685,2,1 +"46",29.8,199.541560658719,1,0,0,1 +"46",29.8,199.541560658719,1,2594.04028856334,1,1 +"46",29.8,199.541560658719,1,1508.0719434181,2,1 +"46",30.25,192.03497256135,1,0,0,1 +"46",30.25,192.03497256135,1,2496.45464329755,1,1 +"46",30.25,192.03497256135,1,1548.40852805206,2,1 +"46",31.5,174.759400628425,1,0,0,1 +"46",31.5,174.759400628425,1,2271.87220816953,1,1 +"46",31.5,174.759400628425,1,1624.30184621138,2,1 +"46",32,169.046604957919,1,0,0,1 +"46",32,169.046604957919,1,2197.60586445295,1,1 +"46",32,169.046604957919,1,1642.71573405569,2,1 +"46",32.5,163.885956000036,1,0,0,1 +"46",32.5,163.885956000036,1,2130.51742800047,1,1 +"46",32.5,163.885956000036,1,1655.71655762053,2,1 +"47",25.5,20.0552822586777,1,0,0,1 +"47",25.5,20.0552822586777,1,260.71866936281,1,1 +"47",25.5,20.0552822586777,1,266.014356568573,2,1 +"47",27.1,64.0125770447382,1,0,0,1 +"47",27.1,64.0125770447382,1,832.163501581597,1,1 +"47",27.1,64.0125770447382,1,337.505186699356,2,1 +"47",32.5,41.7899330762602,1,0,0,1 +"47",32.5,41.7899330762602,1,543.269129991383,1,1 +"47",32.5,41.7899330762602,1,448.371914877069,2,1 +"48",26.6,19.5123424520179,1,0,0,1 +"48",26.6,19.5123424520179,1,253.660451876232,1,1 +"48",26.6,19.5123424520179,1,258.928120649511,2,1 +"48",28.3,62.7777302766499,1,0,0,1 +"48",28.3,62.7777302766499,1,816.110493596449,1,1 +"48",28.3,62.7777302766499,1,335.862690624667,2,1 +"48",28.4,72.7584270496348,1,0,0,1 +"48",28.4,72.7584270496348,1,945.859551645252,1,1 +"48",28.4,72.7584270496348,1,341.707815083531,2,1 +"48",30,95.7621096906827,1,0,0,1 +"48",30,95.7621096906827,1,1244.90742597887,1,1 +"48",30,95.7621096906827,1,498.336535745428,2,1 +"48",32.8,73.5942938168419,1,0,0,1 +"48",32.8,73.5942938168419,1,956.725819618945,1,1 +"48",32.8,73.5942938168419,1,634.63420748559,2,1 +"48",35.2,63.0121628783603,1,0,0,1 +"48",35.2,63.0121628783603,1,819.158117418684,1,1 +"48",35.2,63.0121628783603,1,666.319256963967,2,1 +"49",22.9,15.2911674878246,1,0,0,1 +"49",22.9,15.2911674878246,1,198.78517734172,1,1 +"49",22.9,15.2911674878246,1,202.468274648743,2,1 +"49",24.1,48.8371423680896,1,0,0,1 +"49",24.1,48.8371423680896,1,634.882850785164,1,1 +"49",24.1,48.8371423680896,1,237.036399857945,2,1 +"49",29.6,31.1450851316858,1,0,0,1 +"49",29.6,31.1450851316858,1,404.886106711916,1,1 +"49",29.6,31.1450851316858,1,330.468428263134,2,1 +"5",24.25,25.126905955714,1,0,0,1 +"5",24.25,25.126905955714,1,326.649777424283,1,1 +"5",24.25,25.126905955714,1,333.053907104604,2,1 +"5",29.75,62.9775332175499,1,0,0,1 +"5",29.75,62.9775332175499,1,818.707931828148,1,1 +"5",29.75,62.9775332175499,1,498.310878408091,2,1 +"5",32.75,51.2950399980995,1,0,0,1 +"5",32.75,51.2950399980995,1,666.835519975293,1,1 +"5",32.75,51.2950399980995,1,539.954375832723,2,1 +"50",24.4,14.7250352681066,1,0,0,1 +"50",24.4,14.7250352681066,1,191.425458485386,1,1 +"50",24.4,14.7250352681066,1,195.197137913445,2,1 +"50",25.4,48.3619130279288,1,0,0,1 +"50",25.4,48.3619130279288,1,628.704869363074,1,1 +"50",25.4,48.3619130279288,1,230.879751181247,2,1 +"50",26.4,43.2834368656521,1,0,0,1 +"50",26.4,43.2834368656521,1,562.684679253477,1,1 +"50",26.4,43.2834368656521,1,267.184022362449,2,1 +"51",24,14.873565760885,1,0,0,1 +"51",24,14.873565760885,1,193.356354891505,1,1 +"51",24,14.873565760885,1,197.114022383359,2,1 +"51",29.1,47.0981753779973,1,0,0,1 +"51",29.1,47.0981753779973,1,612.276279913965,1,1 +"51",29.1,47.0981753779973,1,214.314666469559,2,1 +"51",32.5,33.9433084171585,1,0,0,1 +"51",32.5,33.9433084171585,1,441.263009423061,1,1 +"51",32.5,33.9433084171585,1,297.736211268949,2,1 +"52",23.5,25.6048666783561,1,0,0,1 +"52",23.5,25.6048666783561,1,332.863266818629,1,1 +"52",23.5,25.6048666783561,1,339.205860299131,2,1 +"52",24.5,82.7713686067306,1,0,0,1 +"52",24.5,82.7713686067306,1,1076.0277918875,1,1 +"52",24.5,82.7713686067306,1,399.710095636108,2,1 +"52",25,78.177045546991,1,0,0,1 +"52",25,78.177045546991,1,1016.30159211088,1,1 +"52",25,78.177045546991,1,433.297870347944,2,1 +"52",25,78.177045546991,1,0,0,1 +"52",25,78.177045546991,1,1016.30159211088,1,1 +"52",25,78.177045546991,1,433.297870347944,2,1 +"52",25.5,199.530452261218,1,0,0,1 +"52",25.5,199.530452261218,1,2593.89587939584,1,1 +"52",25.5,199.530452261218,1,510.305831114737,2,1 +"52",26.6,230.020689372433,1,0,0,1 +"52",26.6,230.020689372433,1,2990.26896184163,1,1 +"52",26.6,230.020689372433,1,801.604277432789,2,1 +"52",27,218.396059200645,1,0,0,1 +"52",27,218.396059200645,1,2839.14876960839,1,1 +"52",27,218.396059200645,1,894.456459779714,2,1 +"52",28.6,182.136837552542,1,0,0,1 +"52",28.6,182.136837552542,1,2367.77888818304,1,1 +"52",28.6,182.136837552542,1,1158.81227094841,2,1 +"52",29.5,167.218321927381,1,0,0,1 +"52",29.5,167.218321927381,1,2173.83818505596,1,1 +"52",29.5,167.218321927381,1,1250.72325151291,2,1 +"52",31.5,143.30586033076,1,0,0,1 +"52",31.5,143.30586033076,1,1862.97618429988,1,1 +"52",31.5,143.30586033076,1,1360.8907663105,2,1 +"52",31.7,141.435509794789,1,0,0,1 +"52",31.7,141.435509794789,1,1838.66162733225,1,1 +"52",31.7,141.435509794789,1,1366.69796834268,2,1 +"52",32.6,133.888814145927,1,0,0,1 +"52",32.6,133.888814145927,1,1740.55458389705,1,1 +"52",32.6,133.888814145927,1,1384.33792564751,2,1 +"53",24,29.7471315217699,1,0,0,1 +"53",24,29.7471315217699,1,386.712709783009,1,1 +"53",24,29.7471315217699,1,394.228044766717,2,1 +"53",25,98.1044417548665,1,0,0,1 +"53",25,98.1044417548665,1,1275.35774281326,1,1 +"53",25,98.1044417548665,1,455.772145115405,2,1 +"53",25.2,111.058830420158,1,0,0,1 +"53",25.2,111.058830420158,1,1443.76479546205,1,1 +"53",25.2,111.058830420158,1,474.265088464008,2,1 +"53",25.7,210.694320048314,1,0,0,1 +"53",25.7,210.694320048314,1,2739.02616062809,1,1 +"53",25.7,210.694320048314,1,566.070398847506,2,1 +"53",25.7,210.694320048314,1,0,0,1 +"53",25.7,210.694320048314,1,2739.02616062809,1,1 +"53",25.7,210.694320048314,1,566.070398847506,2,1 +"53",26,287.525207973055,1,0,0,1 +"53",26,287.525207973055,1,3737.82770364971,1,1 +"53",26,287.525207973055,1,658.456930799691,2,1 +"53",26,287.525207973055,1,0,0,1 +"53",26,287.525207973055,1,3737.82770364971,1,1 +"53",26,287.525207973055,1,658.456930799691,2,1 +"53",26.3,360.735148595203,1,0,0,1 +"53",26.3,360.735148595203,1,4689.55693173764,1,1 +"53",26.3,360.735148595203,1,782.834685838483,2,1 +"53",26.5,374.568128071979,1,0,0,1 +"53",26.5,374.568128071979,1,4869.38566493572,1,1 +"53",26.5,374.568128071979,1,875.208309902996,2,1 +"53",26.7,421.438741702389,1,0,0,1 +"53",26.7,421.438741702389,1,5478.70364213105,1,1 +"53",26.7,421.438741702389,1,974.234833427253,2,1 +"53",26.8,445.874633708914,1,0,0,1 +"53",26.8,445.874633708914,1,5796.37023821588,1,1 +"53",26.8,445.874633708914,1,1028.37860620711,2,1 +"53",26.9,469.955919106808,1,0,0,1 +"53",26.9,469.955919106808,1,6109.4269483885,1,1 +"53",26.9,469.955919106808,1,1085.55550773448,2,1 +"53",27,493.691897421272,1,0,0,1 +"53",27,493.691897421272,1,6417.99466647653,1,1 +"53",27,493.691897421272,1,1145.66738992531,2,1 +"53",27.3,487.668130419832,1,0,0,1 +"53",27.3,487.668130419832,1,6339.68569545781,1,1 +"53",27.3,487.668130419832,1,1327.31151420294,2,1 +"53",27.7,506.475628760791,1,0,0,1 +"53",27.7,506.475628760791,1,6584.18317389028,1,1 +"53",27.7,506.475628760791,1,1555.06044595071,2,1 +"53",27.75,510.6729874638,1,0,0,1 +"53",27.75,510.6729874638,1,6638.74883702941,1,1 +"53",27.75,510.6729874638,1,1583.96606354588,2,1 +"53",29,454.959300584498,1,0,0,1 +"53",29,454.959300584498,1,5914.47090759848,1,1 +"53",29,454.959300584498,1,2209.32524838757,2,1 +"53",29,454.959300584498,1,0,0,1 +"53",29,454.959300584498,1,5914.47090759848,1,1 +"53",29,454.959300584498,1,2209.32524838757,2,1 +"53",29.3,439.4660080268,1,0,0,1 +"53",29.3,439.4660080268,1,5713.05810434841,1,1 +"53",29.3,439.4660080268,1,2323.55069052708,2,1 +"53",30.3,395.452682766285,1,0,0,1 +"53",30.3,395.452682766285,1,5140.8848759617,1,1 +"53",30.3,395.452682766285,1,2624.9638791794,2,1 +"53",31.7,349.186315913106,1,0,0,1 +"53",31.7,349.186315913106,1,4539.42210687038,1,1 +"53",31.7,349.186315913106,1,2888.76484540162,2,1 +"53",31.75,347.79511460891,1,0,0,1 +"53",31.75,347.79511460891,1,4521.33648991583,1,1 +"53",31.75,347.79511460891,1,2895.52455654045,2,1 +"53",32.3,333.472012971969,1,0,0,1 +"53",32.3,333.472012971969,1,4335.1361686356,1,1 +"53",32.3,333.472012971969,1,2960.00028848284,2,1 +"53",32.8,321.858409823544,1,0,0,1 +"53",32.8,321.858409823544,1,4184.15932770608,1,1 +"53",32.8,321.858409823544,1,3004.51956274093,2,1 +"53",33.1,315.456599160856,1,0,0,1 +"53",33.1,315.456599160856,1,4100.93578909113,1,1 +"53",33.1,315.456599160856,1,3025.61137678492,2,1 +"53",33.25,312.401318720139,1,0,0,1 +"53",33.25,312.401318720139,1,4061.2171433618,1,1 +"53",33.25,312.401318720139,1,3034.72271043135,2,1 +"53",33.3,311.403475924996,1,0,0,1 +"53",33.3,311.403475924996,1,4048.24518702495,1,1 +"53",33.3,311.403475924996,1,3037.55786627618,2,1 +"54",47,6.50086333483274,1,0,0,1 +"54",47,6.50086333483274,1,84.5112233528256,1,1 +"54",47,6.50086333483274,1,86.3787640356877,2,1 +"54",143,9.09015416547886,1,0,0,1 +"54",143,9.09015416547886,1,118.172004151225,1,1 +"54",143,9.09015416547886,1,120.783695294839,2,1 +"54",167.7,16.5294489327907,1,0,0,1 +"54",167.7,16.5294489327907,1,214.88283612628,1,1 +"54",167.7,16.5294489327907,1,218.957170198709,2,1 +"54",192.5,8.93719610289644,1,0,0,1 +"54",192.5,8.93719610289644,1,116.183549337654,1,1 +"54",192.5,8.93719610289644,1,118.751555795538,2,1 +"54",195,41.6545342726652,1,0,0,1 +"54",195,41.6545342726652,1,541.508945544647,1,1 +"54",195,41.6545342726652,1,224.758404832781,2,1 +"54",206,21.8813603342952,1,0,0,1 +"54",206,21.8813603342952,1,284.457684345838,1,1 +"54",206,21.8813603342952,1,277.278747567164,2,1 +"54",264,18.5051716464447,1,0,0,1 +"54",264,18.5051716464447,1,240.567231403781,1,1 +"54",264,18.5051716464447,1,245.162513022837,2,1 +"54",290.25,15.0154175259909,1,0,0,1 +"54",290.25,15.0154175259909,1,195.200427837882,1,1 +"54",290.25,15.0154175259909,1,128.484938881132,2,1 +"54",291.1,106.652683669582,1,0,0,1 +"54",291.1,106.652683669582,1,1386.48488770456,1,1 +"54",291.1,106.652683669582,1,220.903096226355,2,1 +"54",296,62.4026253095568,1,0,0,1 +"54",296,62.4026253095568,1,811.234129024239,1,1 +"54",296,62.4026253095568,1,540.944619251605,2,1 +"55",24,14.873565760885,1,0,0,1 +"55",24,14.873565760885,1,193.356354891505,1,1 +"55",24,14.873565760885,1,197.114022383359,2,1 +"55",24,14.873565760885,1,0,0,1 +"55",24,14.873565760885,1,193.356354891505,1,1 +"55",24,14.873565760885,1,197.114022383359,2,1 +"55",25.2,63.5593440936221,1,0,0,1 +"55",25.2,63.5593440936221,1,826.271473217088,1,1 +"55",25.2,63.5593440936221,1,234.196038653798,2,1 +"55",25.8,80.342372959521,1,0,0,1 +"55",25.8,80.342372959521,1,1044.45084847377,1,1 +"55",25.8,80.342372959521,1,285.661588827192,2,1 +"55",26.2,98.8046621030014,1,0,0,1 +"55",26.2,98.8046621030014,1,1284.46060733902,1,1 +"55",26.2,98.8046621030014,1,323.084481627041,2,1 +"55",27.5,96.7210356765294,1,0,0,1 +"55",27.5,96.7210356765294,1,1257.37346379488,1,1 +"55",27.5,96.7210356765294,1,462.380986294097,2,1 +"55",31,69.7056431404081,1,0,0,1 +"55",31,69.7056431404081,1,906.173360825305,1,1 +"55",31,69.7056431404081,1,628.492806442787,2,1 +"55",31.6,66.8816298654183,1,0,0,1 +"55",31.6,66.8816298654183,1,869.461188250438,1,1 +"55",31.6,66.8816298654183,1,638.582075211743,2,1 +"55",32.2,64.3866757610327,1,0,0,1 +"55",32.2,64.3866757610327,1,837.026784893426,1,1 +"55",32.2,64.3866757610327,1,645.428975022826,2,1 +"56",19.75,23.2003424421549,1,0,0,1 +"56",19.75,23.2003424421549,1,301.604451748013,1,1 +"56",19.75,23.2003424421549,1,305.785254576925,2,1 +"56",20.9,69.380701577856,1,0,0,1 +"56",20.9,69.380701577856,1,901.949120512127,1,1 +"56",20.9,69.380701577856,1,360.663502158082,2,1 +"56",24.7,49.654322336266,1,0,0,1 +"56",24.7,49.654322336266,1,645.506190371458,1,1 +"56",24.7,49.654322336266,1,473.651428685708,2,1 +"56",25.4,98.2978476385128,1,0,0,1 +"56",25.4,98.2978476385128,1,1277.87201930067,1,1 +"56",25.4,98.2978476385128,1,507.355044668708,2,1 +"56",26.2,90.1366168739892,1,0,0,1 +"56",26.2,90.1366168739892,1,1171.77601936186,1,1 +"56",26.2,90.1366168739892,1,564.528452780357,2,1 +"56",31,63.1985406903615,1,0,0,1 +"56",31,63.1985406903615,1,821.5810289747,1,1 +"56",31,63.1985406903615,1,682.694653498987,2,1 +"57",24.5,14.6881695545288,1,0,0,1 +"57",24.5,14.6881695545288,1,190.946204208875,1,1 +"57",24.5,14.6881695545288,1,194.720463605836,2,1 +"57",25.75,46.9347373975435,1,0,0,1 +"57",25.75,46.9347373975435,1,610.151586168066,1,1 +"57",25.75,46.9347373975435,1,240.76198617127,2,1 +"57",26,64.4562067136715,1,0,0,1 +"57",26,64.4562067136715,1,837.93068727773,1,1 +"57",26,64.4562067136715,1,253.922688693899,2,1 +"57",26.25,81.2862931446747,1,0,0,1 +"57",26.25,81.2862931446747,1,1056.72181088077,1,1 +"57",26.25,81.2862931446747,1,273.280938090752,2,1 +"57",31,50.8914452142546,1,0,0,1 +"57",31,50.8914452142546,1,661.58878778531,1,1 +"57",31,50.8914452142546,1,473.16525249386,2,1 +"57",32,47.7264671863123,1,0,0,1 +"57",32,47.7264671863123,1,620.444073422061,1,1 +"57",32,47.7264671863123,1,482.293969650064,2,1 +"58",25,14.5054083845864,1,0,0,1 +"58",25,14.5054083845864,1,188.570308999624,1,1 +"58",25,14.5054083845864,1,192.352472633642,2,1 +"58",25.6,51.1740867759471,1,0,0,1 +"58",25.6,51.1740867759471,1,665.263128087313,1,1 +"58",25.6,51.1740867759471,1,203.967525150537,2,1 +"58",30,33.6606592390952,1,0,0,1 +"58",30,33.6606592390952,1,437.588570108237,1,1 +"58",30,33.6606592390952,1,314.647264458459,2,1 +"59",24,14.873565760885,1,0,0,1 +"59",24,14.873565760885,1,193.356354891505,1,1 +"59",24,14.873565760885,1,197.114022383359,2,1 +"59",27,43.0756078834317,1,0,0,1 +"59",27,43.0756078834317,1,559.982902484612,1,1 +"59",27,43.0756078834317,1,264.463690311764,2,1 +"59",31.5,30.5498099611799,1,0,0,1 +"59",31.5,30.5498099611799,1,397.147529495339,1,1 +"59",31.5,30.5498099611799,1,322.601877087406,2,1 +"6",24.5,14.6881695545288,1,0,0,1 +"6",24.5,14.6881695545288,1,190.946204208875,1,1 +"6",24.5,14.6881695545288,1,194.720463605836,2,1 +"6",24.5,14.6881695545288,1,0,0,1 +"6",24.5,14.6881695545288,1,190.946204208875,1,1 +"6",24.5,14.6881695545288,1,194.720463605836,2,1 +"6",25.1,58.3722569464841,1,0,0,1 +"6",25.1,58.3722569464841,1,758.839340304293,1,1 +"6",25.1,58.3722569464841,1,212.438305526309,2,1 +"6",26,115.023347625538,1,0,0,1 +"6",26,115.023347625538,1,1495.303519132,1,1 +"6",26,115.023347625538,1,318.556332850923,2,1 +"6",26.4,108.8687401451,1,0,0,1 +"6",26.4,108.8687401451,1,1415.2936218863,1,1 +"6",26.4,108.8687401451,1,369.474281191803,2,1 +"6",26.5,107.428358265139,1,0,0,1 +"6",26.5,107.428358265139,1,1396.56865744681,1,1 +"6",26.5,107.428358265139,1,381.16979376148,2,1 +"6",31,68.965613601382,1,0,0,1 +"6",31,68.965613601382,1,896.552976817966,1,1 +"6",31,68.965613601382,1,633.224392474149,2,1 +"6",31.5,66.6714264915475,1,0,0,1 +"6",31.5,66.6714264915475,1,866.728544390118,1,1 +"6",31.5,66.6714264915475,1,641.01429842785,2,1 +"6",31.5,66.6714264915475,1,0,0,1 +"6",31.5,66.6714264915475,1,866.728544390118,1,1 +"6",31.5,66.6714264915475,1,641.01429842785,2,1 +"60",24,20.822992065239,1,0,0,1 +"60",24,20.822992065239,1,270.698896848106,1,1 +"60",24,20.822992065239,1,275.959631336702,2,1 +"60",24,20.822992065239,1,0,0,1 +"60",24,20.822992065239,1,270.698896848106,1,1 +"60",24,20.822992065239,1,275.959631336702,2,1 +"60",24.7,121.204904390608,1,0,0,1 +"60",24.7,121.204904390608,1,1575.66375707791,1,1 +"60",24.7,121.204904390608,1,334.655281060128,2,1 +"60",25.2,154.847918826745,1,0,0,1 +"60",25.2,154.847918826745,1,2013.02294474769,1,1 +"60",25.2,154.847918826745,1,413.75899591353,2,1 +"60",25.5,158.828151950713,1,0,0,1 +"60",25.5,158.828151950713,1,2064.76597535926,1,1 +"60",25.5,158.828151950713,1,470.734344374395,2,1 +"60",26,148.498420594111,1,0,0,1 +"60",26,148.498420594111,1,1930.47946772344,1,1 +"60",26,148.498420594111,1,555.116891760244,2,1 +"60",30,99.5543248467168,1,0,0,1 +"60",30,99.5543248467168,1,1294.20622300732,1,1 +"60",30,99.5543248467168,1,879.077785685263,2,1 +"60",30.2,98.1129499460368,1,0,0,1 +"60",30.2,98.1129499460368,1,1275.46834929848,1,1 +"60",30.2,98.1129499460368,1,884.967963213999,2,1 +"60",31.3,91.1861706749179,1,0,0,1 +"60",31.3,91.1861706749179,1,1185.42021877393,1,1 +"60",31.3,91.1861706749179,1,907.435651801148,2,1 +"61",25.5,14.3252016133412,1,0,0,1 +"61",25.5,14.3252016133412,1,186.227620973435,1,1 +"61",25.5,14.3252016133412,1,190.010254691838,2,1 +"61",25.75,14.2360330101993,1,0,0,1 +"61",25.75,14.2360330101993,1,185.068429132592,1,1 +"61",25.75,14.2360330101993,1,188.848854559715,2,1 +"61",26.75,83.2801787040117,1,0,0,1 +"61",26.75,83.2801787040117,1,1082.64232315215,1,1 +"61",26.75,83.2801787040117,1,252.601948172629,2,1 +"61",27.15,78.9152276540323,1,0,0,1 +"61",27.15,78.9152276540323,1,1025.89795950242,1,1 +"61",27.15,78.9152276540323,1,288.270811581959,2,1 +"61",31.2,52.5437998612697,1,0,0,1 +"61",31.2,52.5437998612697,1,683.069398196506,1,1 +"61",31.2,52.5437998612697,1,463.681561236762,2,1 +"61",31.3,52.1589949403248,1,0,0,1 +"61",31.3,52.1589949403248,1,678.066934224222,1,1 +"61",31.3,52.1589949403248,1,465.28122970594,2,1 +"62",25,20.307571738421,1,0,0,1 +"62",25,20.307571738421,1,263.998432599473,1,1 +"62",25,20.307571738421,1,269.293461687099,2,1 +"62",26.2,67.309110909498,1,0,0,1 +"62",26.2,67.309110909498,1,875.018441823474,1,1 +"62",26.2,67.309110909498,1,318.048934727025,2,1 +"62",31.7,42.6843303026209,1,0,0,1 +"62",31.7,42.6843303026209,1,554.896293934072,1,1 +"62",31.7,42.6843303026209,1,450.512460322726,2,1 +"63",24,20.822992065239,1,0,0,1 +"63",24,20.822992065239,1,270.698896848106,1,1 +"63",24,20.822992065239,1,275.959631336702,2,1 +"63",24,20.822992065239,1,0,0,1 +"63",24,20.822992065239,1,270.698896848106,1,1 +"63",24,20.822992065239,1,275.959631336702,2,1 +"63",24,20.822992065239,1,0,0,1 +"63",24,20.822992065239,1,270.698896848106,1,1 +"63",24,20.822992065239,1,275.959631336702,2,1 +"63",24.3,62.8431360248952,1,0,0,1 +"63",24.3,62.8431360248952,1,816.960768323637,1,1 +"63",24.3,62.8431360248952,1,282.207459872737,2,1 +"63",24.5,103.7005413593,1,0,0,1 +"63",24.5,103.7005413593,1,1348.1070376709,1,1 +"63",24.5,103.7005413593,1,300.221098168216,2,1 +"63",24.5,103.7005413593,1,0,0,1 +"63",24.5,103.7005413593,1,1348.1070376709,1,1 +"63",24.5,103.7005413593,1,300.221098168216,2,1 +"63",24.7,122.056752344878,1,0,0,1 +"63",24.7,122.056752344878,1,1586.73778048341,1,1 +"63",24.7,122.056752344878,1,326.90994810792,2,1 +"63",24.7,122.056752344878,1,0,0,1 +"63",24.7,122.056752344878,1,1586.73778048341,1,1 +"63",24.7,122.056752344878,1,326.90994810792,2,1 +"63",25,162.245930766439,1,0,0,1 +"63",25,162.245930766439,1,2109.1970999637,1,1 +"63",25,162.245930766439,1,377.445910114614,2,1 +"63",25,162.245930766439,1,0,0,1 +"63",25,162.245930766439,1,2109.1970999637,1,1 +"63",25,162.245930766439,1,377.445910114614,2,1 +"63",25.75,239.396076541094,1,0,0,1 +"63",25.75,239.396076541094,1,3112.14899503422,1,1 +"63",25.75,239.396076541094,1,573.155504800531,2,1 +"63",26.1,250.474412895195,1,0,0,1 +"63",26.1,250.474412895195,1,3256.16736763753,1,1 +"63",26.1,250.474412895195,1,674.53244781395,2,1 +"63",26.1,250.474412895195,1,0,0,1 +"63",26.1,250.474412895195,1,3256.16736763753,1,1 +"63",26.1,250.474412895195,1,674.53244781395,2,1 +"63",26.4,289.105269617029,1,0,0,1 +"63",26.4,289.105269617029,1,3758.36850502138,1,1 +"63",26.4,289.105269617029,1,770.193673839056,2,1 +"63",27.1,283.899104357869,1,0,0,1 +"63",27.1,283.899104357869,1,3690.6883566523,1,1 +"63",27.1,283.899104357869,1,1004.61768241514,2,1 +"63",30.75,194.113689710402,1,0,0,1 +"63",30.75,194.113689710402,1,2523.47796623523,1,1 +"63",30.75,194.113689710402,1,1620.52292477092,2,1 +"63",31.25,186.829736783967,1,0,0,1 +"63",31.25,186.829736783967,1,2428.78657819157,1,1 +"63",31.25,186.829736783967,1,1653.33282763459,2,1 +"63",31.25,186.829736783967,1,0,0,1 +"63",31.25,186.829736783967,1,2428.78657819157,1,1 +"63",31.25,186.829736783967,1,1653.33282763459,2,1 +"63",31.75,180.297466228164,1,0,0,1 +"63",31.75,180.297466228164,1,2343.86706096613,1,1 +"63",31.75,180.297466228164,1,1678.61312431811,2,1 +"63",31.9,178.469235511123,1,0,0,1 +"63",31.9,178.469235511123,1,2320.1000616446,1,1 +"63",31.9,178.469235511123,1,1684.89071366355,2,1 +"64",27.1,13.764894371016,1,0,0,1 +"64",27.1,13.764894371016,1,178.943626823209,1,1 +"64",27.1,13.764894371016,1,182.689509708171,2,1 +"64",28.1,47.4275899286468,1,0,0,1 +"64",28.1,47.4275899286468,1,616.558669072408,1,1 +"64",28.1,47.4275899286468,1,218.652120145171,2,1 +"64",36.7,25.9159031741557,1,0,0,1 +"64",36.7,25.9159031741557,1,336.906741264025,1,1 +"64",36.7,25.9159031741557,1,310.501268404083,2,1 +"65",22.2,10.8950064047013,1,0,0,1 +"65",22.2,10.8950064047013,1,141.635083261118,1,1 +"65",22.2,10.8950064047013,1,144.155907131852,2,1 +"65",23.8,41.7111243064052,1,0,0,1 +"65",23.8,41.7111243064052,1,542.244615983268,1,1 +"65",23.8,41.7111243064052,1,203.205006432822,2,1 +"65",24,40.7536398239806,1,0,0,1 +"65",24,40.7536398239806,1,529.797317711748,1,1 +"65",24,40.7536398239806,1,210.292620544965,2,1 +"65",24.5,59.3861825845205,1,0,0,1 +"65",24.5,59.3861825845205,1,772.020373598766,1,1 +"65",24.5,59.3861825845205,1,232.450779695809,2,1 +"65",25,76.5551318425866,1,0,0,1 +"65",25,76.5551318425866,1,995.216713953625,1,1 +"65",25,76.5551318425866,1,267.482210290361,2,1 +"65",25.8,103.370504793229,1,0,0,1 +"65",25.8,103.370504793229,1,1343.81656231197,1,1 +"65",25.8,103.370504793229,1,357.263238906988,2,1 +"65",25.9,102.009880893322,1,0,0,1 +"65",25.9,102.009880893322,1,1326.12845161319,1,1 +"65",25.9,102.009880893322,1,368.276679406691,2,1 +"65",26,100.684259815193,1,0,0,1 +"65",26,100.684259815193,1,1308.89537759751,1,1 +"65",26,100.684259815193,1,378.922380731947,2,1 +"65",26.5,115.373137442829,1,0,0,1 +"65",26.5,115.373137442829,1,1499.85078675678,1,1 +"65",26.5,115.373137442829,1,433.525591095485,2,1 +"65",26.5,115.373137442829,1,0,0,1 +"65",26.5,115.373137442829,1,1499.85078675678,1,1 +"65",26.5,115.373137442829,1,433.525591095485,2,1 +"65",27.25,123.11976563269,1,0,0,1 +"65",27.25,123.11976563269,1,1600.55695322497,1,1 +"65",27.25,123.11976563269,1,520.758368482768,2,1 +"65",28.4,119.197848723385,1,0,0,1 +"65",28.4,119.197848723385,1,1549.57203340401,1,1 +"65",28.4,119.197848723385,1,652.468159098687,2,1 +"65",28.5,117.890479849502,1,0,0,1 +"65",28.5,117.890479849502,1,1532.57623804353,1,1 +"65",28.5,117.890479849502,1,661.758760857296,2,1 +"65",30.3,99.0799605367695,1,0,0,1 +"65",30.3,99.0799605367695,1,1288.039486978,1,1 +"65",30.3,99.0799605367695,1,780.160152011798,2,1 +"65",30.4,98.251904423471,1,0,0,1 +"65",30.4,98.251904423471,1,1277.27475750512,1,1 +"65",30.4,98.251904423471,1,784.511698855621,2,1 +"65",31,93.6593766125386,1,0,0,1 +"65",31,93.6593766125386,1,1217.571895963,1,1 +"65",31,93.6593766125386,1,806.812017379086,2,1 +"65",32.7,83.4614304275443,1,0,0,1 +"65",32.7,83.4614304275443,1,1084.99859555808,1,1 +"65",32.7,83.4614304275443,1,841.859231921314,2,1 +"66",24.5,14.6881695545288,1,0,0,1 +"66",24.5,14.6881695545288,1,190.946204208875,1,1 +"66",24.5,14.6881695545288,1,194.720463605836,2,1 +"66",26.4,43.7012842200821,1,0,0,1 +"66",26.4,43.7012842200821,1,568.116694861067,1,1 +"66",26.4,43.7012842200821,1,263.668492901775,2,1 +"66",30.9,30.8732321590023,1,0,0,1 +"66",30.9,30.8732321590023,1,401.35201806703,1,1 +"66",30.9,30.8732321590023,1,324.434233868065,2,1 +"67",24,13.4238960840023,1,0,0,1 +"67",24,13.4238960840023,1,174.510649092029,1,1 +"67",24,13.4238960840023,1,177.645595770836,2,1 +"67",25.5,56.9991254115316,1,0,0,1 +"67",25.5,56.9991254115316,1,740.988630349911,1,1 +"67",25.5,56.9991254115316,1,258.819977378609,2,1 +"67",30,38.0598502887898,1,0,0,1 +"67",30,38.0598502887898,1,494.778053754268,1,1 +"67",30,38.0598502887898,1,370.795593525831,2,1 +"68",47,5.41738611236062,1,0,0,1 +"68",47,5.41738611236062,1,70.426019460688,1,1 +"68",47,5.41738611236062,1,71.9823033630731,2,1 +"68",47.8,40.9952852453851,1,0,0,1 +"68",47.8,40.9952852453851,1,532.938708190006,1,1 +"68",47.8,40.9952852453851,1,95.8867536523268,2,1 +"68",48,39.831881050873,1,0,0,1 +"68",48,39.831881050873,1,517.814453661349,1,1 +"68",48,39.831881050873,1,105.757911042536,2,1 +"68",48,39.831881050873,1,0,0,1 +"68",48,39.831881050873,1,517.814453661349,1,1 +"68",48,39.831881050873,1,105.757911042536,2,1 +"68",48,39.831881050873,1,0,0,1 +"68",48,39.831881050873,1,517.814453661349,1,1 +"68",48,39.831881050873,1,105.757911042536,2,1 +"68",48,39.831881050873,1,0,0,1 +"68",48,39.831881050873,1,517.814453661349,1,1 +"68",48,39.831881050873,1,105.757911042536,2,1 +"68",48.1,46.899814863046,1,0,0,1 +"68",48.1,46.899814863046,1,609.697593219599,1,1 +"68",48.1,46.899814863046,1,111.055366350858,2,1 +"68",49,172.491539409647,1,0,0,1 +"68",49,172.491539409647,1,2242.39001232541,1,1 +"68",49,172.491539409647,1,293.322059918893,2,1 +"68",49.1,169.891726202656,1,0,0,1 +"68",49.1,169.891726202656,1,2208.59244063453,1,1 +"68",49.1,169.891726202656,1,315.992552717557,2,1 +"68",49.5,160.159748677477,1,0,0,1 +"68",49.5,160.159748677477,1,2082.0767328072,1,1 +"68",49.5,160.159748677477,1,399.624040352846,2,1 +"68",49.5,160.159748677477,1,0,0,1 +"68",49.5,160.159748677477,1,2082.0767328072,1,1 +"68",49.5,160.159748677477,1,399.624040352846,2,1 +"68",49.5,160.159748677477,1,0,0,1 +"68",49.5,160.159748677477,1,2082.0767328072,1,1 +"68",49.5,160.159748677477,1,399.624040352846,2,1 +"68",51,131.562108504428,1,0,0,1 +"68",51,131.562108504428,1,1710.30741055757,1,1 +"68",51,131.562108504428,1,630.084332473617,2,1 +"68",51.5,124.232820598501,1,0,0,1 +"68",51.5,124.232820598501,1,1615.02666778051,1,1 +"68",51.5,124.232820598501,1,683.823456425184,2,1 +"68",53.6,101.622678166023,1,0,0,1 +"68",53.6,101.622678166023,1,1321.0948161583,1,1 +"68",53.6,101.622678166023,1,824.899925258943,2,1 +"68",54,98.4412910633897,1,0,0,1 +"68",54,98.4412910633897,1,1279.73678382407,1,1 +"68",54,98.4412910633897,1,840.255840343953,2,1 +"68",54.75,93.1826242499693,1,0,0,1 +"68",54.75,93.1826242499693,1,1211.3741152496,1,1 +"68",54.75,93.1826242499693,1,861.944939274327,2,1 +"69",25.5,22.9203225813459,1,0,0,1 +"69",25.5,22.9203225813459,1,297.964193557497,1,1 +"69",25.5,22.9203225813459,1,304.01640750694,2,1 +"69",28.25,64.3029050814088,1,0,0,1 +"69",28.25,64.3029050814088,1,835.937766058315,1,1 +"69",28.25,64.3029050814088,1,446.579482462858,2,1 +"69",31.25,51.0548306843452,1,0,0,1 +"69",31.25,51.0548306843452,1,663.712798896488,1,1 +"69",31.25,51.0548306843452,1,507.750655358675,2,1 +"7",24.1,22.4631996719871,1,0,0,1 +"7",24.1,22.4631996719871,1,292.021595735832,1,1 +"7",24.1,22.4631996719871,1,197.23463219008,2,1 +"7",25.2,81.3860720181453,1,0,0,1 +"7",25.2,81.3860720181453,1,1058.01893623589,1,1 +"7",25.2,81.3860720181453,1,281.500400485755,2,1 +"7",26,73.5471826557493,1,0,0,1 +"7",26,73.5471826557493,1,956.113374524741,1,1 +"7",26,73.5471826557493,1,343.193499162186,2,1 +"7",27.1,99.0717293859714,1,0,0,1 +"7",27.1,99.0717293859714,1,1287.93248201763,1,1 +"7",27.1,99.0717293859714,1,444.371961528679,2,1 +"7",28.1,122.944347715233,1,0,0,1 +"7",28.1,122.944347715233,1,1598.27652029804,1,1 +"7",28.1,122.944347715233,1,557.888225282759,2,1 +"7",30.2,98.5623332191301,1,0,0,1 +"7",30.2,98.5623332191301,1,1281.31033184869,1,1 +"7",30.2,98.5623332191301,1,725.107822910408,2,1 +"7",33.3,78.6556849554589,1,0,0,1 +"7",33.3,78.6556849554589,1,1022.52390442097,1,1 +"7",33.3,78.6556849554589,1,807.56997380149,2,1 +"7",51.3,68.8554744532244,1,0,0,1 +"7",51.3,68.8554744532244,1,895.121167891918,1,1 +"7",51.3,68.8554744532244,1,702.714106462792,2,1 +"7",52.75,96.4072905275354,1,0,0,1 +"7",52.75,96.4072905275354,1,1253.29477685796,1,1 +"7",52.75,96.4072905275354,1,759.171944055775,2,1 +"7",57.25,72.7141201703261,1,0,0,1 +"7",57.25,72.7141201703261,1,945.283562214239,1,1 +"7",57.25,72.7141201703261,1,824.960428626746,2,1 +"70",23.5,10.5431803969702,1,0,0,1 +"70",23.5,10.5431803969702,1,137.061345160612,1,1 +"70",23.5,10.5431803969702,1,139.673001299642,2,1 +"70",24.5,34.0823282498302,1,0,0,1 +"70",24.5,34.0823282498302,1,443.070267247793,1,1 +"70",24.5,34.0823282498302,1,164.586509967809,2,1 +"70",29,23.0126793311636,1,0,0,1 +"70",29,23.0126793311636,1,299.164831305127,1,1 +"70",29,23.0126793311636,1,227.762750593455,2,1 +"71",26.1,23.9907787712371,1,0,0,1 +"71",26.1,23.9907787712371,1,311.880124026083,1,1 +"71",26.1,23.9907787712371,1,318.297452560955,2,1 +"71",27.4,78.3888892329684,1,0,0,1 +"71",27.4,78.3888892329684,1,1019.05556002859,1,1 +"71",27.4,78.3888892329684,1,400.268715639862,2,1 +"71",30.5,58.7434372575742,1,0,0,1 +"71",30.5,58.7434372575742,1,763.664684348465,1,1 +"71",30.5,58.7434372575742,1,519.909348981777,2,1 +"72",24.5,14.6881695545288,1,0,0,1 +"72",24.5,14.6881695545288,1,190.946204208875,1,1 +"72",24.5,14.6881695545288,1,194.720463605836,2,1 +"72",25,51.3900349292725,1,0,0,1 +"72",25,51.3900349292725,1,668.070454080543,1,1 +"72",25,51.3900349292725,1,206.775081099258,2,1 +"72",25.7,82.8900097516702,1,0,0,1 +"72",25.7,82.8900097516702,1,1077.57012677171,1,1 +"72",25.7,82.8900097516702,1,264.079721999624,2,1 +"72",26,79.6294359156929,1,0,0,1 +"72",26,79.6294359156929,1,1035.18266690401,1,1 +"72",26,79.6294359156929,1,290.625691911609,2,1 +"72",29.3,56.216440929416,1,0,0,1 +"72",29.3,56.216440929416,1,730.813732082407,1,1 +"72",29.3,56.216440929416,1,452.67463862703,2,1 +"72",30.8,50.2933511496905,1,0,0,1 +"72",30.8,50.2933511496905,1,653.813564945977,1,1 +"72",30.8,50.2933511496905,1,477.908609657943,2,1 +"73",15.6,12.9690424665389,1,0,0,1 +"73",15.6,12.9690424665389,1,168.597552065006,1,1 +"73",15.6,12.9690424665389,1,168.17425909915,2,1 +"73",16.1,75.4923462435548,1,0,0,1 +"73",16.1,75.4923462435548,1,981.400501166212,1,1 +"73",16.1,75.4923462435548,1,190.854537814156,2,1 +"73",19.5,50.9352955511893,1,0,0,1 +"73",19.5,50.9352955511893,1,662.158842165461,1,1 +"73",19.5,50.9352955511893,1,374.238875403728,2,1 +"73",22.7,40.4033213087199,1,0,0,1 +"73",22.7,40.4033213087199,1,525.243177013359,1,1 +"73",22.7,40.4033213087199,1,417.411736261314,2,1 +"73",24,37.720430370331,1,0,0,1 +"73",24,37.720430370331,1,490.365594814303,1,1 +"73",24,37.720430370331,1,419.326000364533,2,1 +"73",24.3,37.1827119676334,1,0,0,1 +"73",24.3,37.1827119676334,1,483.375255579234,1,1 +"73",24.3,37.1827119676334,1,419.013716016971,2,1 +"73",25,98.727966676172,1,0,0,1 +"73",25,98.727966676172,1,1283.46356679024,1,1 +"73",25,98.727966676172,1,441.944342131597,2,1 +"73",26,166.709600469447,1,0,0,1 +"73",26,166.709600469447,1,2167.22480610282,1,1 +"73",26,166.709600469447,1,594.981897773282,2,1 +"73",26,166.709600469447,1,0,0,1 +"73",26,166.709600469447,1,2167.22480610282,1,1 +"73",26,166.709600469447,1,594.981897773282,2,1 +"73",26.1,182.840355671865,1,0,0,1 +"73",26.1,182.840355671865,1,2376.92462373425,1,1 +"73",26.1,182.840355671865,1,613.920321112835,2,1 +"73",26.3,203.763675454938,1,0,0,1 +"73",26.3,203.763675454938,1,2648.9277809142,1,1 +"73",26.3,203.763675454938,1,656.781151817711,2,1 +"73",26.5,245.255826371182,1,0,0,1 +"73",26.5,245.255826371182,1,3188.32574282536,1,1 +"73",26.5,245.255826371182,1,708.182732297208,2,1 +"73",27.25,311.352647171099,1,0,0,1 +"73",27.25,311.352647171099,1,4047.58441322429,1,1 +"73",27.25,311.352647171099,1,972.944491212463,2,1 +"73",27.5,301.029940314491,1,0,0,1 +"73",27.5,301.029940314491,1,3913.38922408838,1,1 +"73",27.5,301.029940314491,1,1057.39274782857,2,1 +"73",27.5,301.029940314491,1,0,0,1 +"73",27.5,301.029940314491,1,3913.38922408838,1,1 +"73",27.5,301.029940314491,1,1057.39274782857,2,1 +"73",27.8,289.499577708083,1,0,0,1 +"73",27.8,289.499577708083,1,3763.49451020508,1,1 +"73",27.8,289.499577708083,1,1149.72544751623,2,1 +"73",30.5,217.093756665909,1,0,0,1 +"73",30.5,217.093756665909,1,2822.21883665682,1,1 +"73",30.5,217.093756665909,1,1653.49727561836,2,1 +"73",31,208.135723789986,1,0,0,1 +"73",31,208.135723789986,1,2705.76440926981,1,1 +"73",31,208.135723789986,1,1700.8798010628,2,1 +"73",31.25,204.025198292617,1,0,0,1 +"73",31.25,204.025198292617,1,2652.32757780403,1,1 +"73",31.25,204.025198292617,1,1720.83169074996,2,1 +"73",31.8,195.746984592556,1,0,0,1 +"73",31.8,195.746984592556,1,2544.71079970323,1,1 +"73",31.8,195.746984592556,1,1757.01899808837,2,1 +"73",32,192.974039565681,1,0,0,1 +"73",32,192.974039565681,1,2508.66251435385,1,1 +"73",32,192.974039565681,1,1767.80170554008,2,1 +"74",23.6,21.0333754739932,1,0,0,1 +"74",23.6,21.0333754739932,1,273.433881161912,1,1 +"74",23.6,21.0333754739932,1,278.665921182921,2,1 +"74",23.7,20.9805470625289,1,0,0,1 +"74",23.7,20.9805470625289,1,272.747111812876,1,1 +"74",23.7,20.9805470625289,1,277.987238896347,2,1 +"74",24.8,163.644259197949,1,0,0,1 +"74",24.8,163.644259197949,1,2127.37536957334,1,1 +"74",24.8,163.644259197949,1,436.381027113475,2,1 +"74",25.9,141.428154060998,1,0,0,1 +"74",25.9,141.428154060998,1,1838.56600279297,1,1 +"74",25.9,141.428154060998,1,616.506582313026,2,1 +"74",26.5,131.721629535004,1,0,0,1 +"74",26.5,131.721629535004,1,1712.38118395505,1,1 +"74",26.5,131.721629535004,1,689.475359071798,2,1 +"74",27.7,162.31319498622,1,0,0,1 +"74",27.7,162.31319498622,1,2110.07153482086,1,1 +"74",27.7,162.31319498622,1,864.690072082175,2,1 +"74",28,157.027613912589,1,0,0,1 +"74",28,157.027613912589,1,2041.35898086366,1,1 +"74",28,157.027613912589,1,902.273340807252,2,1 +"74",29.6,134.544368826465,1,0,0,1 +"74",29.6,134.544368826465,1,1749.07679474405,1,1 +"74",29.6,134.544368826465,1,1043.68692010505,2,1 +"74",29.8,132.287259437174,1,0,0,1 +"74",29.8,132.287259437174,1,1719.73437268326,1,1 +"74",29.8,132.287259437174,1,1055.68641241979,2,1 +"74",30.2,128.070637476679,1,0,0,1 +"74",30.2,128.070637476679,1,1664.91828719683,1,1 +"74",30.2,128.070637476679,1,1076.66424699236,2,1 +"74",34,101.544812682165,1,0,0,1 +"74",34,101.544812682165,1,1320.08256486815,1,1 +"74",34,101.544812682165,1,1141.97039398423,2,1 +"75",23.1,21.2998947108679,1,0,0,1 +"75",23.1,21.2998947108679,1,276.898631241283,1,1 +"75",23.1,21.2998947108679,1,282.080219265553,2,1 +"75",25.1,61.2437905640379,1,0,0,1 +"75",25.1,61.2437905640379,1,796.169277332493,1,1 +"75",25.1,61.2437905640379,1,382.488947426042,2,1 +"75",29.3,44.3334837673674,1,0,0,1 +"75",29.3,44.3334837673674,1,576.335288975777,1,1 +"75",29.3,44.3334837673674,1,461.734218449179,2,1 +"76",24.3,14.7620070397996,1,0,0,1 +"76",24.3,14.7620070397996,1,191.906091517395,1,1 +"76",24.3,14.7620070397996,1,195.674833180084,2,1 +"76",25.7,59.6818737461227,1,0,0,1 +"76",25.7,59.6818737461227,1,775.864358699596,1,1 +"76",25.7,59.6818737461227,1,264.828600391022,2,1 +"76",26.6,53.799697600779,1,0,0,1 +"76",26.6,53.799697600779,1,699.396068810127,1,1 +"76",26.6,53.799697600779,1,308.168738298045,2,1 +"76",27.4,85.2549692930892,1,0,0,1 +"76",27.4,85.2549692930892,1,1108.31460081016,1,1 +"76",27.4,85.2549692930892,1,361.616226517051,2,1 +"76",28.1,114.126636234227,1,0,0,1 +"76",28.1,114.126636234227,1,1483.64627104496,1,1 +"76",28.1,114.126636234227,1,438.29590114286,2,1 +"76",29,102.289206515826,1,0,0,1 +"76",29,102.289206515826,1,1329.75968470573,1,1 +"76",29,102.289206515826,1,529.013773049804,2,1 +"76",29.3,106.534862386138,1,0,0,1 +"76",29.3,106.534862386138,1,1384.95321101979,1,1 +"76",29.3,106.534862386138,1,553.959083611124,2,1 +"76",30.25,123.450694260903,1,0,0,1 +"76",30.25,123.450694260903,1,1604.85902539174,1,1 +"76",30.25,123.450694260903,1,657.487643612423,2,1 +"76",31.1,112.792598044265,1,0,0,1 +"76",31.1,112.792598044265,1,1466.30377457545,1,1 +"76",31.1,112.792598044265,1,730.883868991767,2,1 +"76",33.3,93.4359231994085,1,0,0,1 +"76",33.3,93.4359231994085,1,1214.66700159231,1,1 +"76",33.3,93.4359231994085,1,836.235181034115,2,1 +"76",34,89.021347490183,1,0,0,1 +"76",34,89.021347490183,1,1157.27751737238,1,1 +"76",34,89.021347490183,1,852.140383233994,2,1 +"77",23.5,21.0863607939403,1,0,0,1 +"77",23.5,21.0863607939403,1,274.122690321224,1,1 +"77",23.5,21.0863607939403,1,279.346002599284,2,1 +"77",25.1,67.85805029133,1,0,0,1 +"77",25.1,67.85805029133,1,882.15465378729,1,1 +"77",25.1,67.85805029133,1,325.199117667769,2,1 +"77",30.1,44.3943231248116,1,0,0,1 +"77",30.1,44.3943231248116,1,577.126200622551,1,1 +"77",30.1,44.3943231248116,1,454.967746929962,2,1 +"78",25.5,20.0552822586777,1,0,0,1 +"78",25.5,20.0552822586777,1,260.71866936281,1,1 +"78",25.5,20.0552822586777,1,266.014356568573,2,1 +"78",27.3,64.603212909408,1,0,0,1 +"78",27.3,64.603212909408,1,839.841767822304,1,1 +"78",27.3,64.603212909408,1,330.258353666562,2,1 +"78",32.7,41.8883261687615,1,0,0,1 +"78",32.7,41.8883261687615,1,544.548240193899,1,1 +"78",32.7,41.8883261687615,1,446.533927146316,2,1 +"79",25.1,24.5975807040119,1,0,0,1 +"79",25.1,24.5975807040119,1,319.768549152155,1,1 +"79",25.1,24.5975807040119,1,326.199335376103,2,1 +"79",25.6,24.2920823931438,1,0,0,1 +"79",25.6,24.2920823931438,1,315.797071110869,1,1 +"79",25.6,24.2920823931438,1,322.226358364367,2,1 +"79",27.1,139.027989964454,1,0,0,1 +"79",27.1,139.027989964454,1,1807.3638695379,1,1 +"79",27.1,139.027989964454,1,447.681086701254,2,1 +"79",27.25,136.247648819133,1,0,0,1 +"79",27.25,136.247648819133,1,1771.21943464873,1,1 +"79",27.25,136.247648819133,1,470.406721458337,2,1 +"79",32.1,85.0505296573575,1,0,0,1 +"79",32.1,85.0505296573575,1,1105.65688554565,1,1 +"79",32.1,85.0505296573575,1,802.521185038849,2,1 +"79",32.75,81.568293796294,1,0,0,1 +"79",32.75,81.568293796294,1,1060.38781935182,1,1 +"79",32.75,81.568293796294,1,812.60823645536,2,1 +"8",24.1,25.2216616617207,1,0,0,1 +"8",24.1,25.2216616617207,1,327.881601602369,1,1 +"8",24.1,25.2216616617207,1,334.276572252318,2,1 +"8",25.3,81.8216106004167,1,0,0,1 +"8",25.3,81.8216106004167,1,1063.68093780542,1,1 +"8",25.3,81.8216106004167,1,397.330655097252,2,1 +"8",25.75,122.157105620613,1,0,0,1 +"8",25.75,122.157105620613,1,1588.04237306797,1,1 +"8",25.75,122.157105620613,1,439.543242500031,2,1 +"8",27,121.184258261814,1,0,0,1 +"8",27,121.184258261814,1,1575.39535740358,1,1 +"8",27,121.184258261814,1,601.680136791128,2,1 +"8",28.75,101.117750421938,1,0,0,1 +"8",28.75,101.117750421938,1,1314.5307554852,1,1 +"8",28.75,101.117750421938,1,736.925918920678,2,1 +"8",29.25,159.479312565302,1,0,0,1 +"8",29.25,159.479312565302,1,2073.23106334893,1,1 +"8",29.25,159.479312565302,1,785.750366224192,2,1 +"8",31.7,125.136706031475,1,0,0,1 +"8",31.7,125.136706031475,1,1626.77717840918,1,1 +"8",31.7,125.136706031475,1,1008.27129808003,2,1 +"8",31.75,124.623365771905,1,0,0,1 +"8",31.75,124.623365771905,1,1620.10375503476,1,1 +"8",31.75,124.623365771905,1,1010.88613612441,2,1 +"8",34.75,102.031480017621,1,0,0,1 +"8",34.75,102.031480017621,1,1326.40924022907,1,1 +"8",34.75,102.031480017621,1,1085.857785438,2,1 +"80",93.4,18.1887843830806,1,0,0,1 +"80",93.4,18.1887843830806,1,236.454196980048,1,1 +"80",93.4,18.1887843830806,1,241.678682016969,2,1 +"80",94.75,64.0843245912825,1,0,0,1 +"80",94.75,64.0843245912825,1,833.096219686672,1,1 +"80",94.75,64.0843245912825,1,300.132973727274,2,1 +"80",96,55.8514456070923,1,0,0,1 +"80",96,55.8514456070923,1,726.0687928922,1,1 +"80",96,55.8514456070923,1,358.610328958633,2,1 +"80",97.7,90.7842574191272,1,0,0,1 +"80",97.7,90.7842574191272,1,1180.19534644865,1,1 +"80",97.7,90.7842574191272,1,502.247027965486,2,1 +"80",100,73.1187148330544,1,0,0,1 +"80",100,73.1187148330544,1,950.543292829707,1,1 +"80",100,73.1187148330544,1,610.58417783723,2,1 +"81",23.9,10.437678227824,1,0,0,1 +"81",23.9,10.437678227824,1,135.689816961712,1,1 +"81",23.9,10.437678227824,1,138.317044879661,2,1 +"81",25,33.9543954308706,1,0,0,1 +"81",25,33.9543954308706,1,441.407140601318,1,1 +"81",25,33.9543954308706,1,162.928916287274,2,1 +"81",25.7,31.37240746884,1,0,0,1 +"81",25.7,31.37240746884,1,407.84129709492,1,1 +"81",25.7,31.37240746884,1,181.650146151945,2,1 +"81",25.8,33.7199201888037,1,0,0,1 +"81",25.8,33.7199201888037,1,438.358962454448,1,1 +"81",25.8,33.7199201888037,1,184.060526021586,2,1 +"81",26.8,75.5322990370866,1,0,0,1 +"81",26.8,75.5322990370866,1,981.919887482125,1,1 +"81",26.8,75.5322990370866,1,260.965137222056,2,1 +"81",26.9,74.5380053549409,1,0,0,1 +"81",26.9,74.5380053549409,1,968.994069614231,1,1 +"81",26.9,74.5380053549409,1,269.0138107518,2,1 +"81",27.6,93.6482951437604,1,0,0,1 +"81",27.6,93.6482951437604,1,1217.42783686889,1,1 +"81",27.6,93.6482951437604,1,332.219237370725,2,1 +"81",28.3,85.7144185955125,1,0,0,1 +"81",28.3,85.7144185955125,1,1114.28744174166,1,1 +"81",28.3,85.7144185955125,1,394.608996645043,2,1 +"81",29.5,75.0055380867463,1,0,0,1 +"81",29.5,75.0055380867463,1,975.071995127701,1,1 +"81",29.5,75.0055380867463,1,471.352667467858,2,1 +"81",32.1,60.1177665306885,1,0,0,1 +"81",32.1,60.1177665306885,1,781.53096489895,1,1 +"81",32.1,60.1177665306885,1,551.964217559633,2,1 +"81",32.5,58.5013070609221,1,0,0,1 +"81",32.5,58.5013070609221,1,760.516991791988,1,1 +"81",32.5,58.5013070609221,1,557.560649499373,2,1 +"81",33.8,54.0659599135794,1,0,0,1 +"81",33.8,54.0659599135794,1,702.857478876533,1,1 +"81",33.8,54.0659599135794,1,567.740255052391,2,1 +"82",21.1,22.4085279750763,1,0,0,1 +"82",21.1,22.4085279750763,1,291.310863675992,1,1 +"82",21.1,22.4085279750763,1,296.070760925746,2,1 +"82",22.1,69.4465482790889,1,0,0,1 +"82",22.1,69.4465482790889,1,902.805127628156,1,1 +"82",22.1,69.4465482790889,1,345.575220306074,2,1 +"82",24,57.181980396123,1,0,0,1 +"82",24,57.181980396123,1,743.365745149599,1,1 +"82",24,57.181980396123,1,427.406761710874,2,1 +"82",24.25,82.3054473609988,1,0,0,1 +"82",24.25,82.3054473609988,1,1069.97081569298,1,1 +"82",24.25,82.3054473609988,1,439.45419952684,2,1 +"82",25.5,140.573658440084,1,0,0,1 +"82",25.5,140.573658440084,1,1827.4575597211,1,1 +"82",25.5,140.573658440084,1,619.179486097569,2,1 +"82",26,132.466094594462,1,0,0,1 +"82",26,132.466094594462,1,1722.05922972801,1,1 +"82",26,132.466094594462,1,680.236815190464,2,1 +"82",26,132.466094594462,1,0,0,1 +"82",26,132.466094594462,1,1722.05922972801,1,1 +"82",26,132.466094594462,1,680.236815190464,2,1 +"82",26.9,170.579282902163,1,0,0,1 +"82",26.9,170.579282902163,1,2217.53067772812,1,1 +"82",26.9,170.579282902163,1,797.510581439825,2,1 +"82",27.1,177.269563019153,1,0,0,1 +"82",27.1,177.269563019153,1,2304.50431924899,1,1 +"82",27.1,177.269563019153,1,828.275001274157,2,1 +"82",27.25,190.090028440279,1,0,0,1 +"82",27.25,190.090028440279,1,2471.17036972363,1,1 +"82",27.25,190.090028440279,1,853.697832846387,2,1 +"82",29.5,169.665016733009,1,0,0,1 +"82",29.5,169.665016733009,1,2205.64521752911,1,1 +"82",29.5,169.665016733009,1,1191.52419900841,2,1 +"82",30.5,155.460147854823,1,0,0,1 +"82",30.5,155.460147854823,1,2020.9819221127,1,1 +"82",30.5,155.460147854823,1,1270.70094294605,2,1 +"82",31,149.488012406835,1,0,0,1 +"82",31,149.488012406835,1,1943.34416128885,1,1 +"82",31,149.488012406835,1,1298.80269894376,2,1 +"82",32.75,132.976822220788,1,0,0,1 +"82",32.75,132.976822220788,1,1728.69868887024,1,1 +"82",32.75,132.976822220788,1,1353.36378800379,2,1 +"82",33,131.058937626513,1,0,0,1 +"82",33,131.058937626513,1,1703.76618914467,1,1 +"82",33,131.058937626513,1,1356.844626111,2,1 +"83",24,20.822992065239,1,0,0,1 +"83",24,20.822992065239,1,270.698896848106,1,1 +"83",24,20.822992065239,1,275.959631336702,2,1 +"83",24,20.822992065239,1,0,0,1 +"83",24,20.822992065239,1,270.698896848106,1,1 +"83",24,20.822992065239,1,275.959631336702,2,1 +"83",24.4,103.917825254978,1,0,0,1 +"83",24.4,103.917825254978,1,1350.93172831471,1,1 +"83",24.4,103.917825254978,1,299.389228312383,2,1 +"83",24.75,119.538003533293,1,0,0,1 +"83",24.75,119.538003533293,1,1553.99404593281,1,1 +"83",24.75,119.538003533293,1,349.152355103068,2,1 +"83",25,115.510009985061,1,0,0,1 +"83",25,115.510009985061,1,1501.6301298058,1,1 +"83",25,115.510009985061,1,382.422203461999,2,1 +"83",25.2,144.325659839721,1,0,0,1 +"83",25.2,144.325659839721,1,1876.23357791637,1,1 +"83",25.2,144.325659839721,1,411.269404429066,2,1 +"83",25.25,154.044887355059,1,0,0,1 +"83",25.25,154.044887355059,1,2002.58353561576,1,1 +"83",25.25,154.044887355059,1,420.07071273752,2,1 +"83",25.25,154.044887355059,1,0,0,1 +"83",25.25,154.044887355059,1,2002.58353561576,1,1 +"83",25.25,154.044887355059,1,420.07071273752,2,1 +"83",25.5,201.492196549843,1,0,0,1 +"83",25.5,201.492196549843,1,2619.39855514795,1,1 +"83",25.5,201.492196549843,1,474.342783236104,2,1 +"83",25.7,216.956234066334,1,0,0,1 +"83",25.7,216.956234066334,1,2820.43104286234,1,1 +"83",25.7,216.956234066334,1,526.106639486121,2,1 +"83",26.3,341.817404318948,1,0,0,1 +"83",26.3,341.817404318948,1,4443.62625614632,1,1 +"83",26.3,341.817404318948,1,748.968811466535,2,1 +"83",26.5,332.008763387807,1,0,0,1 +"83",26.5,332.008763387807,1,4316.1139240415,1,1 +"83",26.5,332.008763387807,1,832.688092089137,2,1 +"83",26.8,318.237071744393,1,0,0,1 +"83",26.8,318.237071744393,1,4137.08193267711,1,1 +"83",26.8,318.237071744393,1,948.338858251415,2,1 +"83",30.1,219.898090919667,1,0,0,1 +"83",30.1,219.898090919667,1,2858.67518195567,1,1 +"83",30.1,219.898090919667,1,1663.99458867161,2,1 +"83",31,204.178217220809,1,0,0,1 +"83",31,204.178217220809,1,2654.31682387052,1,1 +"83",31,204.178217220809,1,1744.46777516993,2,1 +"83",31.4,198.147820384554,1,0,0,1 +"83",31.4,198.147820384554,1,2575.9216649992,1,1 +"83",31.4,198.147820384554,1,1770.57201151978,2,1 +"83",31.6,195.323740169283,1,0,0,1 +"83",31.6,195.323740169283,1,2539.20862220068,1,1 +"83",31.6,195.323740169283,1,1781.71071908106,2,1 +"83",31.6,195.323740169283,1,0,0,1 +"83",31.6,195.323740169283,1,2539.20862220068,1,1 +"83",31.6,195.323740169283,1,1781.71071908106,2,1 +"83",31.8,192.617803216378,1,0,0,1 +"83",31.8,192.617803216378,1,2504.03144181292,1,1 +"83",31.8,192.617803216378,1,1791.67294327734,2,1 +"83",33,178.525555625774,1,0,0,1 +"83",33,178.525555625774,1,2320.83222313506,1,1 +"83",33,178.525555625774,1,1830.34770711166,2,1 +"84",22.1,15.6038065398348,1,0,0,1 +"84",22.1,15.6038065398348,1,202.849485017853,1,1 +"84",22.1,15.6038065398348,1,206.436321013304,2,1 +"84",23.8,45.964867401014,1,0,0,1 +"84",23.8,45.964867401014,1,597.543276213182,1,1 +"84",23.8,45.964867401014,1,264.617213164748,2,1 +"84",24.25,43.8521137248448,1,0,0,1 +"84",24.25,43.8521137248448,1,570.077478422982,1,1 +"84",24.25,43.8521137248448,1,278.952988542557,2,1 +"84",25.1,75.2652373815925,1,0,0,1 +"84",25.1,75.2652373815925,1,978.448085960702,1,1 +"84",25.1,75.2652373815925,1,333.098171674049,2,1 +"84",29.75,49.5543881963007,1,0,0,1 +"84",29.75,49.5543881963007,1,644.20704655191,1,1 +"84",29.75,49.5543881963007,1,485.641404058201,2,1 +"85",47,12.0200386633677,1,0,0,1 +"85",47,12.0200386633677,1,156.26050262378,1,1 +"85",47,12.0200386633677,1,159.71390513718,2,1 +"85",47.25,11.9459610140004,1,0,0,1 +"85",47.25,11.9459610140004,1,155.297493182006,1,1 +"85",47.25,11.9459610140004,1,158.7296833091,2,1 +"85",47.25,11.9459610140004,1,0,0,1 +"85",47.25,11.9459610140004,1,155.297493182006,1,1 +"85",47.25,11.9459610140004,1,158.7296833091,2,1 +"85",47.5,69.5925956159121,1,0,0,1 +"85",47.5,69.5925956159121,1,904.703743006857,1,1 +"85",47.5,69.5925956159121,1,168.088462486709,2,1 +"85",48,210.388246484117,1,0,0,1 +"85",48,210.388246484117,1,2735.04720429352,1,1 +"85",48,210.388246484117,1,273.228534535795,2,1 +"85",48,210.388246484117,1,0,0,1 +"85",48,210.388246484117,1,2735.04720429352,1,1 +"85",48,210.388246484117,1,273.228534535795,2,1 +"85",48.3,299.852415929855,1,0,0,1 +"85",48.3,299.852415929855,1,3898.08140708811,1,1 +"85",48.3,299.852415929855,1,380.37683220261,2,1 +"85",48.5,342.082091975948,1,0,0,1 +"85",48.5,342.082091975948,1,4447.06719568733,1,1 +"85",48.5,342.082091975948,1,469.651743108679,2,1 +"85",48.75,329.148766730021,1,0,0,1 +"85",48.75,329.148766730021,1,4278.93396749027,1,1 +"85",48.75,329.148766730021,1,583.259223291747,2,1 +"85",48.8,326.664573366244,1,0,0,1 +"85",48.8,326.664573366244,1,4246.63945376117,1,1 +"85",48.8,326.664573366244,1,604.896860324207,2,1 +"85",49,342.76659010586,1,0,0,1 +"85",49,342.76659010586,1,4455.96567137618,1,1 +"85",49,342.76659010586,1,692.053719169821,2,1 +"85",49,342.76659010586,1,0,0,1 +"85",49,342.76659010586,1,4455.96567137618,1,1 +"85",49,342.76659010586,1,692.053719169821,2,1 +"85",49.1,350.684897113299,1,0,0,1 +"85",49.1,350.684897113299,1,4558.90366247289,1,1 +"85",49.1,350.684897113299,1,736.57808547298,2,1 +"85",49.25,362.402584014071,1,0,0,1 +"85",49.25,362.402584014071,1,4711.23359218293,1,1 +"85",49.25,362.402584014071,1,804.483605779556,2,1 +"85",49.3,366.266920088947,1,0,0,1 +"85",49.3,366.266920088947,1,4761.46996115632,1,1 +"85",49.3,366.266920088947,1,827.406301896565,2,1 +"85",49.5,355.809935139399,1,0,0,1 +"85",49.5,355.809935139399,1,4625.52915681219,1,1 +"85",49.5,355.809935139399,1,916.418127717291,2,1 +"85",50,395.505735722822,1,0,0,1 +"85",50,395.505735722822,1,5141.57456439669,1,1 +"85",50,395.505735722822,1,1131.62339972494,2,1 +"85",50,395.505735722822,1,0,0,1 +"85",50,395.505735722822,1,5141.57456439669,1,1 +"85",50,395.505735722822,1,1131.62339972494,2,1 +"85",50.6,487.307185783434,1,0,0,1 +"85",50.6,487.307185783434,1,6334.99341518464,1,1 +"85",50.6,487.307185783434,1,1458.15187783773,2,1 +"85",51.2,449.840579768214,1,0,0,1 +"85",51.2,449.840579768214,1,5847.92753698678,1,1 +"85",51.2,449.840579768214,1,1762.66478065918,2,1 +"85",52,408.127156526419,1,0,0,1 +"85",52,408.127156526419,1,5305.65303484345,1,1 +"85",52,408.127156526419,1,2082.23780452581,2,1 +"85",52.25,396.739046211488,1,0,0,1 +"85",52.25,396.739046211488,1,5157.60760074934,1,1 +"85",52.25,396.739046211488,1,2164.897564128,2,1 +"85",53.3,355.926014530637,1,0,0,1 +"85",53.3,355.926014530637,1,4627.03818889828,1,1 +"85",53.3,355.926014530637,1,2439.21820904464,2,1 +"85",54,333.933165864811,1,0,0,1 +"85",54,333.933165864811,1,4341.13115624254,1,1 +"85",54,333.933165864811,1,2568.32017692912,2,1 +"85",54.3,325.555259371703,1,0,0,1 +"85",54.3,325.555259371703,1,4232.21837183214,1,1 +"85",54.3,325.555259371703,1,2612.94224824849,2,1 +"85",54.5,320.281240504631,1,0,0,1 +"85",54.5,320.281240504631,1,4163.6561265602,1,1 +"85",54.5,320.281240504631,1,2639.52771750324,2,1 +"85",55.3,301.390624258937,1,0,0,1 +"85",55.3,301.390624258937,1,3918.07811536618,1,1 +"85",55.3,301.390624258937,1,2723.6154621019,2,1 +"85",55.7,293.10589706569,1,0,0,1 +"85",55.7,293.10589706569,1,3810.37666185397,1,1 +"85",55.7,293.10589706569,1,2754.04759526858,2,1 +"85",56,287.332681593025,1,0,0,1 +"85",56,287.332681593025,1,3735.32486070932,1,1 +"85",56,287.332681593025,1,2772.51245672088,2,1 +"85",56.5,278.460584743363,1,0,0,1 +"85",56.5,278.460584743363,1,3619.98760166372,1,1 +"85",56.5,278.460584743363,1,2795.93241512939,2,1 diff --git a/inst/Examples/Runs/sylvain_sim/theta.csv b/inst/Examples/Runs/sylvain_sim/theta.csv new file mode 100644 index 000000000..34579001a --- /dev/null +++ b/inst/Examples/Runs/sylvain_sim/theta.csv @@ -0,0 +1,3 @@ +"CL","Q","V1","V2" +0.35,1.58,9,7 +0.65,1.58,13,11 diff --git a/inst/Examples/src/Dapto_Lyon.csv b/inst/Examples/src/Dapto_Lyon.csv new file mode 100644 index 000000000..34f0dd5ab --- /dev/null +++ b/inst/Examples/src/Dapto_Lyon.csv @@ -0,0 +1,980 @@ +ID,TIME,EVID,DOSE,DUR,ADDL,II,OUT,AGE,CREAT,WEIGHT,MALE,CLCR,OCC +1,0,1,700,0.5,-1,24,.,76,78,106,0,90,1 +1,24.7,0,.,0,.,0,33.6,76,78,106,0,90,1 +1,27.3,1,700,0.5,.,0,.,76,78,106,0,90,1 +1,28.7,0,.,0,.,0,77.6,76,78,106,0,90,1 +1,33.3,0,.,0,.,0,62.5,76,78,106,0,90,1 +1,0,4,700,0.5,-1,24,.,76,67,105,0,104,2 +1,24.7,0,.,0,.,0,31.6,76,67,105,0,104,2 +1,25.5,1,700,0.5,.,0,.,76,67,105,0,104,2 +1,26.7,0,.,0,.,0,92.4,76,67,105,0,104,2 +1,31.7,0,.,0,.,0,62.1,76,67,105,0,104,2 +2,0,1,700,0.5,-1,24,.,51,65,80,1,135,1 +2,23.8,0,.,0,.,0,12.8,51,65,80,1,135,1 +2,23.8,1,700,0.5,.,0,.,51,65,80,1,135,1 +2,24.7,0,.,0,.,0,75.7,51,65,80,1,135,1 +2,28.75,0,.,0,.,0,41.9,51,65,80,1,135,1 +3,0,1,700,0.5,-1,24,.,83,78,84,0,64,1 +3,22,0,.,0,.,0,52.2,83,78,84,0,64,1 +3,22.25,1,700,0.5,.,0,.,83,78,84,0,64,1 +3,23.6,0,.,0,.,0,107.8,83,78,84,0,64,1 +3,29.2,0,.,0,.,0,77.7,83,78,84,0,64,1 +3,0,4,700,0.5,-1,24,.,83,72,84,0,69,2 +3,26.1,0,.,0,.,0,39.5,83,72,84,0,69,2 +3,26.1,1,700,0.5,.,0,.,83,72,84,0,69,2 +3,27,0,.,0,.,0,107.4,83,72,84,0,69,2 +3,32.7,0,.,0,.,0,79.1,83,72,84,0,69,2 +4,0,1,700,0.5,-1,24,.,60,63,94,1,147,1 +4,14.5,0,.,0,.,0,11.9,60,63,94,1,147,1 +4,14.8,1,700,0.5,.,0,.,60,63,94,1,147,1 +4,18.1,0,.,0,.,0,59.6,60,63,94,1,147,1 +4,19.8,0,.,0,.,0,41.3,60,63,94,1,147,1 +5,0,1,850,0.5,-1,24,.,45,78,104,1,156,1 +5,24.25,0,.,0,.,0,26.1,45,78,104,1,156,1 +5,26.25,1,850,0.5,.,0,.,45,78,104,1,156,1 +5,29.75,0,.,0,.,0,75.6,45,78,104,1,156,1 +5,32.75,0,.,0,.,0,60.97,45,78,104,1,156,1 +6,0,1,500,0.5,-1,24,.,63,62,66,1,101,1 +6,24.5,0,.,0,.,0,13.6,63,62,66,1,101,1 +6,24.5,1,500,0.5,.,0,.,63,62,66,1,101,1 +6,26.5,0,.,0,.,0,61,63,62,66,1,101,1 +6,31.5,0,.,0,.,0,35.6,63,62,66,1,101,1 +6,0,4,500,0.5,-1,24,.,63,68,70,1,97,3 +6,24.5,0,.,0,.,0,11.9,63,68,70,1,97,3 +6,25,1,500,0.5,.,0,.,63,68,70,1,97,3 +6,26,0,.,0,.,0,55.6,63,68,70,1,97,3 +6,31,0,.,0,.,0,32.1,63,68,70,1,97,3 +6,0,4,500,0.5,-1,24,.,63,61,68,1,106,2 +6,25.1,0,.,0,.,0,8.8,63,61,68,1,106,2 +6,25.25,1,500,0.5,.,0,.,63,61,68,1,106,2 +6,26.4,0,.,0,.,0,52,63,61,68,1,106,2 +6,31.5,0,.,0,.,0,31.9,63,61,68,1,106,2 +7,0,1,500,0.5,-1,24,.,89,99,70,0,38,1 +7,26,0,.,0,.,0,44.7,89,99,70,0,38,1 +7,26.1,1,500,0.5,.,0,.,89,99,70,0,38,1 +7,30.2,0,.,0,.,0,73.9,89,99,70,0,38,1 +7,33.3,0,.,0,.,0,67.7,89,99,70,0,38,1 +7,0,4,500,0.5,-1,24,.,89,99,72,0,39,2 +7,24.1,0,.,0,.,0,47.1,89,99,72,0,39,2 +7,24.2,1,500,0.5,.,0,.,89,99,72,0,39,2 +7,25.2,0,.,0,.,0,86.3,89,99,72,0,39,2 +7,0,4,500,0.5,.,24,.,89,152,72,0,25,3 +7,24,1,500,0.5,.,24,.,89,152,72,0,25,3 +7,48,1,500,0.5,.,24,.,89,152,72,0,25,3 +7,51.3,0,.,0,.,0,50.7,89,152,72,0,25,3 +7,51.5,1,500,0.5,.,0,.,89,152,72,0,25,3 +7,52.75,0,.,0,.,0,95,89,152,72,0,25,3 +7,57.25,0,.,0,.,0,70,89,152,72,0,25,3 +7,0,4,500,0.5,-1,24,.,89,127,70,0,29,4 +7,27.1,0,.,0,.,0,40.1,89,127,70,0,29,4 +7,27.2,1,500,0.5,.,0,.,89,127,70,0,29,4 +7,28.1,0,.,0,.,0,86.9,89,127,70,0,29,4 +8,0,1,850,0.5,-1,24,.,29,50,110,1,300,1 +8,24.1,0,.,0,.,0,13.9,29,50,110,1,300,1 +8,24.25,1,850,0.5,.,0,.,29,50,110,1,300,1 +8,25.75,0,.,0,.,0,70.1,29,50,110,1,300,1 +8,31.75,0,.,0,.,0,38.8,29,50,110,1,300,1 +8,0,4,850,0.5,-1,24,.,29,74,112,1,207,2 +8,25.3,0,.,0,.,0,14.8,29,74,112,1,207,2 +8,25.4,1,850,0.5,.,0,.,29,74,112,1,207,2 +8,27,0,.,0,.,0,64.1,29,74,112,1,207,2 +8,31.7,0,.,0,.,0,41.1,29,74,112,1,207,2 +8,0,4,850,0.5,-1,24,.,29,84,109,1,177,3 +8,28.75,0,.,0,.,0,5,29,84,109,1,177,3 +8,28.75,1,850,0.5,.,0,.,29,84,109,1,177,3 +8,29.25,0,.,0,.,0,92.9,29,84,109,1,177,3 +8,34.75,0,.,0,.,0,39.6,29,84,109,1,177,3 +11,0,1,500,0.5,-1,24,.,20,57,55,0,120,1 +11,26.6,0,.,0,.,0,14.6,20,57,55,0,120,1 +11,26.7,1,500,0.5,.,0,.,20,57,55,0,120,1 +11,27.25,0,.,0,.,0,67.5,20,57,55,0,120,1 +11,32,0,.,0,.,0,41.9,20,57,55,0,120,1 +12,0,1,500,0.5,-1,24,.,50,64,70,1,121,1 +12,26.7,0,.,0,.,0,6.9,50,64,70,1,121,1 +12,27.75,1,500,0.5,.,0,.,50,64,70,1,121,1 +12,28.75,0,.,0,.,0,74.1,50,64,70,1,121,1 +12,34.25,0,.,0,.,0,37.4,50,64,70,1,121,1 +13,0,1,700,0.5,-1,24,.,58,64,86,1,136,1 +13,25,0,.,0,.,0,9.8,58,64,86,1,136,1 +13,27.5,1,700,0.5,.,0,.,58,64,86,1,136,1 +13,28.5,0,.,0,.,0,50.4,58,64,86,1,136,1 +13,34.5,0,.,0,.,0,25.3,58,64,86,1,136,1 +13,0,4,700,0.5,-1,24,.,58,70,86,1,124,2 +13,24.75,0,.,0,.,0,5.9,58,70,86,1,124,2 +13,26.75,1,700,0.5,.,0,.,58,70,86,1,124,2 +13,27.75,0,.,0,.,0,62.8,58,70,86,1,124,2 +13,32.2,0,.,0,.,0,42.4,58,70,86,1,124,2 +13,0,4,700,0.5,-1,24,.,58,71,95,1,135,3 +13,24.3,0,.,0,.,0,6.2,58,71,95,1,135,3 +13,25.5,1,700,0.5,.,0,.,58,71,95,1,135,3 +13,27,0,.,0,.,0,68.4,58,71,95,1,135,3 +13,32.75,0,.,0,.,0,38.5,58,71,95,1,135,3 +14,0,1,500,0.5,-1,24,.,51,82,76,1,101,1 +14,22.9,0,.,0,.,0,16.2,51,82,76,1,101,1 +14,23.2,1,500,0.5,.,0,.,51,82,76,1,101,1 +14,24.2,0,.,0,.,0,73.7,51,82,76,1,101,1 +14,29.75,0,.,0,.,0,43.7,51,82,76,1,101,1 +14,0,4,500,0.5,-1,24,.,51,70,76,1,119,2 +14,21.9,0,.,0,.,0,14.7,51,70,76,1,119,2 +14,22,1,500,0.5,.,0,.,51,70,76,1,119,2 +14,24.25,0,.,0,.,0,60.2,51,70,76,1,119,2 +14,28.1,0,.,0,.,0,44.8,51,70,76,1,119,2 +15,0,1,600,0.5,-1,24,.,67,100,79,1,71,1 +15,23.5,0,.,0,.,0,21.1,67,100,79,1,71,1 +15,23.5,1,600,0.5,.,0,.,67,100,79,1,71,1 +15,24.5,0,.,0,.,0,64.1,67,100,79,1,71,1 +15,29.75,0,.,0,.,0,52,67,100,79,1,71,1 +16,0,1,500,0.5,-1,24,.,68,98,71,1,64,1 +16,23.9,0,.,0,.,0,22.2,68,98,71,1,64,1 +16,24,1,500,0.5,.,0,.,68,98,71,1,64,1 +16,25.25,0,.,0,.,0,71,68,98,71,1,64,1 +16,30.25,0,.,0,.,0,49.9,68,98,71,1,64,1 +16,0,4,500,0.5,-1,24,.,68,89,71,1,71,2 +16,19.7,0,.,0,.,0,34.4,68,89,71,1,71,2 +16,19.75,1,500,0.5,.,0,.,68,89,71,1,71,2 +16,21,0,.,0,.,0,88.3,68,89,71,1,71,2 +16,26,0,.,0,.,0,61,68,89,71,1,71,2 +17,0,1,500,0.5,-1,24,.,62,133,71,1,51,1 +17,24.5,0,.,0,.,0,18.8,62,133,71,1,51,1 +17,24.7,1,500,0.5,.,0,.,62,133,71,1,51,1 +17,25.75,0,.,0,.,0,48.3,62,133,71,1,51,1 +17,31.3,0,.,0,.,0,33.6,62,133,71,1,51,1 +18,0,1,500,0.5,-1,24,.,84,41,77,0,109,1 +18,24.75,0,.,0,.,0,20,84,41,77,0,109,1 +18,25.25,1,500,0.5,.,0,.,84,41,77,0,109,1 +18,26.75,0,.,0,.,0,54.1,84,41,77,0,109,1 +18,31,0,.,0,.,0,43.1,84,41,77,0,109,1 +18,0,4,500,0.5,-1,24,.,84,39,77,0,115,2 +18,28.75,0,.,0,.,0,11,84,39,77,0,115,2 +18,29.3,1,500,0.5,.,0,.,84,39,77,0,115,2 +18,30.3,0,.,0,.,0,55.8,84,39,77,0,115,2 +18,33.3,0,.,0,.,0,42.6,84,39,77,0,115,2 +19,0,1,500,0.5,-1,24,.,67,40,72,0,137,1 +19,24,0,.,0,.,0,8.9,67,40,72,0,137,1 +19,24,1,500,0.5,.,0,.,67,40,72,0,137,1 +19,25.3,0,.,0,.,0,59.3,67,40,72,0,137,1 +19,27.8,0,.,0,.,0,41.4,67,40,72,0,137,1 +20,0,1,850,0.5,-1,24,.,84,57,88,1,106,1 +20,25.8,0,.,0,.,0,13.3,84,57,88,1,106,1 +20,27.4,1,850,0.5,.,0,.,84,57,88,1,106,1 +20,31.4,0,.,0,.,0,61.4,84,57,88,1,106,1 +21,0,1,350,0.5,-1,24,.,79,39,52,0,85,1 +21,25.9,0,.,0,.,0,6.6,79,39,52,0,85,1 +21,26.7,1,350,0.5,.,0,.,79,39,52,0,85,1 +21,27.8,0,.,0,.,0,42.4,79,39,52,0,85,1 +21,32,0,.,0,.,0,27.3,79,39,52,0,85,1 +21,0,4,350,0.5,-1,24,.,79,39,52,0,85,2 +21,22.25,0,.,0,.,0,15.1,79,39,52,0,85,2 +21,22.5,1,350,0.5,.,0,.,79,39,52,0,85,2 +21,23.5,0,.,0,.,0,65,79,39,52,0,85,2 +21,29,0,.,0,.,0,41.3,79,39,29,0,47,2 +21,0,4,350,0.5,-1,24,.,79,50,50,0,63,3 +21,24,0,.,0,.,0,12.1,79,50,50,0,63,3 +21,24.25,1,350,0.5,.,0,.,79,50,50,0,63,3 +21,25.25,0,.,0,.,0,72.4,79,50,50,0,63,3 +21,29.75,0,.,0,.,0,41.1,79,50,50,0,63,3 +21,0,4,350,0.5,-1,24,.,79,50,50,0,63,4 +21,22.25,0,.,0,.,0,15.1,79,50,50,0,63,4 +21,22.3,1,350,0.5,.,0,.,79,50,50,0,63,4 +21,23.5,0,.,0,.,0,65,79,50,50,0,63,4 +21,29,0,.,0,.,0,41.3,79,50,50,0,63,4 +21,0,4,350,0.5,-1,24,.,79,59,50,0,54,5 +21,20.75,0,.,0,.,0,17,79,59,50,0,54,5 +21,20.75,1,350,0.5,.,0,.,79,59,50,0,54,5 +21,21.25,0,.,0,.,0,50.4,79,59,50,0,54,5 +21,27.3,0,.,0,.,0,29,79,59,50,0,54,5 +22,0,1,350,0.5,-1,48,.,68,162,91,1,50,1 +22,47.5,0,.,0,.,0,4,68,162,91,1,50,1 +22,48,1,350,0.5,.,0,.,68,162,91,1,50,1 +22,49.25,0,.,0,.,0,19.1,68,162,91,1,50,1 +22,55,0,.,0,.,0,13,68,162,91,1,50,1 +22,0,4,350,0.5,-1,24,.,68,162,91,1,50,2 +22,24.8,0,.,0,.,0,5.4,68,162,91,1,50,2 +22,24.8,1,350,0.5,.,0,.,68,162,91,1,50,2 +22,26,0,.,0,.,0,32.6,68,162,91,1,50,2 +22,31,0,.,0,.,0,19.5,68,162,91,1,50,2 +22,0,4,700,0.5,-1,24,.,68,76,91,1,106,3 +22,24.5,0,.,0,.,0,10.4,68,76,91,1,106,3 +22,24.5,1,700,0.5,.,0,.,68,76,91,1,106,3 +22,25.5,0,.,0,.,0,58.5,68,76,91,1,106,3 +22,31,0,.,0,.,0,30.2,68,76,91,1,106,3 +23,0,1,350,0.5,-1,24,.,41,99,52,0,54,1 +23,21.6,0,.,0,.,0,36.9,41,99,52,0,54,1 +23,21.6,1,350,0.5,.,0,.,41,99,52,0,54,1 +23,22.3,0,.,0,.,0,81.2,41,99,52,0,54,1 +23,27.3,0,.,0,.,0,57.8,41,99,52,0,54,1 +23,0,4,350,0.5,-1,24,.,41,108,49,0,47,2 +23,23.75,0,.,0,.,0,39.7,41,108,49,0,47,2 +24,0,1,700,0.5,-1,24,.,66,112,80,1,65,1 +24,24.25,0,.,0,.,0,20.1,66,112,80,1,65,1 +24,25.9,1,700,0.5,.,0,.,66,112,80,1,65,1 +24,26.25,0,.,0,.,0,87.5,66,112,80,1,65,1 +24,31.8,0,.,0,.,0,49.5,66,112,80,1,65,1 +26,0,1,700,0.5,-1,24,.,66,75,140,1,170,1 +26,23.75,0,.,0,.,0,34.1,66,75,140,1,170,1 +26,27.8,1,700,0.5,.,0,.,66,75,140,1,170,1 +26,28.3,0,.,0,.,0,104.1,66,75,140,1,170,1 +26,32.4,0,.,0,.,0,70.3,66,75,140,1,170,1 +26,0,4,500,0.5,-1,24,.,66,76,140,1,168,2 +26,24.2,0,.,0,.,0,19.6,66,76,140,1,168,2 +26,27,1,500,0.5,.,0,.,66,76,140,1,168,2 +26,30,0,.,0,.,0,55.1,66,76,140,1,168,2 +26,35,0,.,0,.,0,39.6,66,76,140,1,168,2 +26,0,4,800,0.5,-1,24,.,66,90,135,1,137,3 +26,24.25,0,.,0,.,0,18.7,66,90,135,1,137,3 +26,24.25,1,800,0.5,.,0,.,66,90,135,1,137,3 +26,25.75,0,.,0,.,0,38.4,66,90,135,1,137,3 +26,30.75,0,.,0,.,0,27.4,66,90,135,1,137,3 +26,0,4,500,0.5,-1,24,.,66,83,135,1,148,4 +26,25.4,0,.,0,.,0,17.8,66,83,135,1,148,4 +26,25.5,1,500,0.5,.,0,.,66,83,135,1,148,4 +26,27,0,.,0,.,0,48.9,66,83,135,1,148,4 +26,32,0,.,0,.,0,32.5,66,83,135,1,148,4 +26,0,4,500,0.5,-1,24,.,66,83,130,1,143,5 +26,23,0,.,0,.,0,14.1,66,83,130,1,143,5 +26,23.2,1,500,0.5,.,0,.,66,83,130,1,143,5 +26,26.5,0,.,0,.,0,34.6,66,83,130,1,143,5 +26,30.7,0,.,0,.,0,28.1,66,83,130,1,143,5 +27,0,1,700,0.5,-1,24,.,79,73,65,0,56,1 +27,24.5,0,.,0,.,0,37.6,79,73,65,0,56,1 +27,24.5,1,700,0.5,.,0,.,79,73,65,0,56,1 +27,25.5,0,.,0,.,0,83.6,79,73,65,0,56,1 +27,30,0,.,0,.,0,59.4,79,73,65,0,56,1 +28,0,1,700,0.5,-1,24,.,77,50.2,59,0,77,1 +28,25,0,.,0,.,0,30.5,77,50.2,59,0,77,1 +28,25.2,1,700,0.5,.,0,.,77,50.2,59,0,77,1 +28,26.25,0,.,0,.,0,83.2,77,50.2,59,0,77,1 +28,30.9,0,.,0,.,0,64.3,77,50.2,59,0,77,1 +29,0,1,500,0.5,-1,24,.,65,56,64,0,89,1 +29,24.25,0,.,0,.,0,23.7,65,56,64,0,89,1 +29,24.3,1,500,0.5,.,0,.,65,56,64,0,89,1 +29,25.5,0,.,0,.,0,66.8,65,56,64,0,89,1 +29,31.5,0,.,0,.,0,41.5,65,56,64,0,89,1 +29,0,4,500,0.5,-1,24,.,65,40,62,0,121,2 +29,25.8,0,.,0,.,0,24.1,65,40,62,0,121,2 +29,26,1,500,0.5,.,0,.,65,40,62,0,121,2 +29,27.6,0,.,0,.,0,52.2,65,40,62,0,121,2 +29,32.6,0,.,0,.,0,45,65,40,62,0,121,2 +30,0,1,500,0.5,-1,24,.,58,56,67,1,121,1 +30,24.2,0,.,0,.,0,7.2,58,56,67,1,121,1 +30,24.2,1,500,0.5,.,0,.,58,56,67,1,121,1 +30,25.4,0,.,0,.,0,49,58,56,67,1,121,1 +30,31,0,.,0,.,0,27.6,58,56,67,1,121,1 +30,0,4,500,0.5,-1,24,.,58,47,69,1,148,2 +30,24.2,0,.,0,.,0,8.7,58,47,69,1,148,2 +30,24.25,1,500,0.5,.,0,.,58,47,69,1,148,2 +30,25.25,0,.,0,.,0,44.3,58,47,69,1,148,2 +30,30.75,0,.,0,.,0,24.9,58,47,69,1,148,2 +30,0,4,500,0.5,-1,24,.,58,50,69,1,139,3 +30,26.8,0,.,0,.,0,9.8,58,50,69,1,139,3 +30,26.8,1,500,0.5,.,0,.,58,50,69,1,139,3 +30,31.2,0,.,0,.,0,39.5,58,50,69,1,139,3 +30,33.3,0,.,0,.,0,32.4,58,50,69,1,139,3 +31,0,1,500,0.5,-1,24,.,50,38,60,1,175,1 +31,25,0,.,0,.,0,7.6,50,38,60,1,175,1 +31,25.1,1,500,0.5,.,0,.,50,38,60,1,175,1 +31,26.1,0,.,0,.,0,58.6,50,38,60,1,175,1 +31,30,0,.,0,.,0,29.4,50,38,60,1,175,1 +31,0,4,500,0.5,-1,24,.,50,38,60,1,175,2 +31,23.5,0,.,0,.,0,6.6,50,38,60,1,175,2 +31,24.1,1,500,0.5,.,0,.,50,38,60,1,175,2 +31,25.4,0,.,0,.,0,41.7,50,38,60,1,175,2 +31,29.4,0,.,0,.,0,22.9,50,38,60,1,175,2 +32,0,1,850,0.5,-1,24,.,60,72,100,0,116,1 +32,24,0,.,0,.,0,25.6,60,72,100,0,116,1 +32,24,1,850,0.5,.,0,.,60,72,100,0,116,1 +32,26.25,0,.,0,.,0,79.3,60,72,100,0,116,1 +32,30.5,0,.,0,.,0,61.4,60,72,100,0,116,1 +32,0,4,850,0.5,-1,24,.,60,57,98,0,143,2 +32,27.3,0,.,0,.,0,23.1,60,57,98,0,143,2 +32,27.3,1,850,0.5,.,0,.,60,57,98,0,143,2 +32,28.75,0,.,0,.,0,58.3,60,57,98,0,143,2 +32,34.25,0,.,0,.,0,46.5,60,57,98,0,143,2 +32,0,4,850,0.5,-1,24,.,60,72,97,0,112,3 +32,26.5,0,.,0,.,0,32.52,60,72,97,0,112,3 +33,0,1,700,0.5,-1,24,.,67,127,102,1,72,1 +33,24.6,0,.,0,.,0,24.1,67,127,102,1,72,1 +33,24.7,1,700,0.5,.,0,.,67,127,102,1,72,1 +33,26,0,.,0,.,0,67.6,67,127,102,1,72,1 +33,31,0,.,0,.,0,49.7,67,127,102,1,72,1 +34,0,1,750,0.5,-1,24,.,66,47,90,1,174,1 +34,24,0,.,0,.,0,12.3,66,47,90,1,174,1 +34,24.7,1,750,0.5,.,0,.,66,47,90,1,174,1 +34,25.7,0,.,0,.,0,52.9,66,47,90,1,174,1 +34,31.7,0,.,0,.,0,28.5,66,47,90,1,174,1 +36,0,1,500,0.5,-1,24,.,67,72,63,0,66,1 +36,24.3,0,.,0,.,0,14.2,67,72,63,0,66,1 +37,0,1,500,0.5,-1,24,.,88,101,60,1,38,1 +37,24.5,0,.,0,.,0,4.3,88,101,60,1,38,1 +37,0,4,500,0.5,-1,24,.,88,137,60,1,28,2 +37,20.2,0,.,0,.,0,29.2,88,137,60,1,28,2 +37,20.25,1,500,0.5,.,0,.,88,137,60,1,28,2 +37,20.9,0,.,0,.,0,116.5,88,137,60,1,28,2 +37,25.3,0,.,0,.,0,90.6,88,137,60,1,28,2 +37,0,4,500,0.5,-1,24,.,88,95,60,1,40,3 +37,21.3,0,.,0,.,0,16.1,88,95,60,1,40,3 +37,21.3,1,500,0.5,.,0,.,88,95,60,1,40,3 +37,22.2,0,.,0,.,0,80.1,88,95,60,1,40,3 +38,0,1,500,0.5,-1,24,.,70,55,70,1,110,1 +38,24,0,.,0,.,0,18.6,70,55,70,1,110,1 +38,24,1,500,0.5,.,0,.,70,55,70,1,110,1 +38,25,0,.,0,.,0,59.4,70,55,70,1,110,1 +38,30.5,0,.,0,.,0,40.4,70,55,70,1,110,1 +38,0,4,500,0.5,-1,24,.,70,68,70,1,89,2 +38,24.3,0,.,0,.,0,16.8,70,68,70,1,89,2 +38,24.7,1,500,0.5,.,0,.,70,68,70,1,89,2 +38,25.7,0,.,0,.,0,57.8,70,68,70,1,89,2 +38,30.3,0,.,0,.,0,34.7,70,68,70,1,89,2 +38,0,4,500,0.5,-1,24,.,70,46,70,1,131,3 +38,24.2,0,.,0,.,0,16.1,70,46,70,1,131,3 +38,24.2,1,500,0.5,.,0,.,70,46,70,1,131,3 +38,25.1,0,.,0,.,0,56.5,70,46,70,1,131,3 +38,30,0,.,0,.,0,34.7,70,46,70,1,131,3 +38,0,4,500,0.5,-1,24,.,70,62,69,1,96,4 +38,24.5,0,.,0,.,0,14.8,70,62,69,1,96,4 +38,24.7,1,500,0.5,.,0,.,70,62,69,1,96,4 +38,25.7,0,.,0,.,0,63.5,70,62,69,1,96,4 +38,31.2,0,.,0,.,0,42.2,70,62,69,1,96,4 +39,0,1,700,0.5,-1,24,.,64,52,112,0,170,1 +39,24.75,0,.,0,.,0,20.8,64,52,112,0,170,1 +39,25,1,700,0.5,.,0,.,64,52,112,0,170,1 +39,26.2,0,.,0,.,0,96.1,64,52,112,0,170,1 +39,30.5,0,.,0,.,0,61.7,64,52,112,0,170,1 +40,0,1,700,0.5,-1,24,.,69,87,83,1,83,1 +40,25.25,0,.,0,.,0,22.4,69,87,83,1,83,1 +40,25.4,1,700,0.5,.,0,.,69,87,83,1,83,1 +40,26.4,0,.,0,.,0,69.2,69,87,83,1,83,1 +40,29.9,0,.,0,.,0,47.2,69,87,83,1,83,1 +40,0,4,700,0.5,-1,24,.,69,81,84,1,91,2 +40,26.75,0,.,0,.,0,23,69,81,84,1,91,2 +40,26.8,1,700,0.5,.,0,.,69,81,84,1,91,2 +40,28.1,0,.,0,.,0,96.4,69,81,84,1,91,2 +40,32.75,0,.,0,.,0,63.2,69,81,84,1,91,2 +41,0,1,350,0.5,-1,24,.,68,36,44,0,92,1 +41,23,0,.,0,.,0,9.2,68,36,44,0,92,1 +41,23.7,1,350,0.5,.,0,.,68,36,44,0,92,1 +41,24.7,0,.,0,.,0,45.2,68,36,44,0,92,1 +41,29,0,.,0,.,0,28.5,68,36,44,0,92,1 +42,0,1,500,0.5,-1,24,.,55,68,63,0,82,1 +42,24.3,0,.,0,.,0,55.9,55,68,63,0,82,1 +42,24.3,1,500,0.5,.,0,.,55,68,63,0,82,1 +42,25.25,0,.,0,.,0,104,55,68,63,0,82,1 +42,0,4,500,0.5,-1,24,.,55,64,64.3,0,89,2 +42,27,0,.,0,.,0,33.5,55,64,64.3,0,89,2 +42,27.2,1,500,0.5,.,0,.,55,64,64.3,0,89,2 +42,28.3,0,.,0,.,0,78.4,55,64,64.3,0,89,2 +42,32,0,.,0,.,0,61.6,55,64,64.3,0,89,2 +43,0,1,500,0.5,-1,24,.,77,74.1,78,0,69,1 +43,24.4,0,.,0,.,0,27.4,77,74.1,78,0,69,1 +43,24.5,1,500,0.5,.,0,.,77,74.1,78,0,69,1 +43,25.7,0,.,0,.,0,89.3,77,74.1,78,0,69,1 +43,31,0,.,0,.,0,60.8,77,74.1,78,0,69,1 +44,0,1,700,0.5,-1,24,.,29,56,84,1,205,1 +44,25.6,0,.,0,.,0,8.8,29,56,84,1,205,1 +44,27,1,700,0.5,.,0,.,29,56,84,1,205,1 +44,29.2,0,.,0,.,0,69,29,56,84,1,205,1 +44,32.75,0,.,0,.,0,47.4,29,56,84,1,205,1 +44,0,4,700,0.5,-1,24,.,29,56,84,1,205,2 +44,24.5,0,.,0,.,0,8,29,56,84,1,205,2 +44,25.3,1,700,0.5,.,0,.,29,56,84,1,205,2 +44,26.5,0,.,0,.,0,87,29,56,84,1,205,2 +44,31.8,0,.,0,.,0,42.3,29,56,84,1,205,2 +45,0,1,700,0.5,-1,24,.,58,95,75,1,80,1 +45,24.5,0,.,0,.,0,16.3,58,95,75,1,80,1 +45,24.8,1,700,0.5,.,0,.,58,95,75,1,80,1 +45,25.8,0,.,0,.,0,88.9,58,95,75,1,80,1 +45,30.7,0,.,0,.,0,52.3,58,95,75,1,80,1 +46,0,1,850,0.5,-1,24,.,42,65,104.4,0,164,1 +46,24,0,.,0,.,0,16.3,42,65,104.4,0,164,1 +46,24,1,850,0.5,.,0,.,42,65,104.4,0,164,1 +46,25.25,0,.,0,.,0,139,42,65,104.4,0,164,1 +46,29.8,0,.,0,.,0,67.7,42,65,104.4,0,164,1 +46,0,4,850,0.5,-1,24,.,42,72,104.4,0,148,2 +46,24.25,0,.,0,.,0,12,42,72,104.4,0,148,2 +46,24.25,1,850,0.5,.,0,.,42,72,104.4,0,148,2 +46,25.25,0,.,0,.,0,101.9,42,72,104.4,0,148,2 +46,30.25,0,.,0,.,0,50.4,42,72,104.4,0,148,2 +46,0,4,850,0.5,-1,24,.,43,77,104,0,136,3 +46,23.6,0,.,0,.,0,7.9,43,77,104,0,136,3 +46,23.75,1,850,0.5,.,0,.,43,77,104,0,136,3 +46,26.6,0,.,0,.,0,82.8,43,77,104,0,136,3 +46,31.5,0,.,0,.,0,49.9,43,77,104,0,136,3 +46,0,4,850,0.5,-1,24,.,43,70,98,0,141,4 +46,26.2,0,.,0,.,0,7.1,43,70,98,0,141,4 +46,26.25,1,850,0.5,.,0,.,43,70,98,0,141,4 +46,27.75,0,.,0,.,0,77.5,43,70,98,0,141,4 +46,32.5,0,.,0,.,0,44,43,70,98,0,141,4 +46,0,4,850,0.5,-1,24,.,43,70,98,0,141,5 +46,25.25,0,.,0,.,0,7.5,43,70,98,0,141,5 +46,25.75,1,850,0.5,.,0,.,43,70,98,0,141,5 +46,27.5,0,.,0,.,0,91.9,43,70,98,0,141,5 +46,32,0,.,0,.,0,51.2,43,70,98,0,141,5 +47,0,1,700,0.5,-1,24,.,61,48,76,0,130,1 +47,25.5,0,.,0,.,0,20.2,61,48,76,0,130,1 +47,25.7,1,700,0.5,.,0,.,61,48,76,0,130,1 +47,27.1,0,.,0,.,0,79.6,61,48,76,0,130,1 +47,32.5,0,.,0,.,0,54.3,61,48,76,0,130,1 +48,0,1,700,0.5,-1,24,.,27,52,84,0,190,1 +48,26.6,0,.,0,.,0,6.5,27,52,84,0,190,1 +48,26.8,1,700,0.5,.,0,.,27,52,84,0,190,1 +48,28.4,0,.,0,.,0,79.7,27,52,84,0,190,1 +48,32.8,0,.,0,.,0,43.4,27,52,84,0,190,1 +48,0,4,700,0.5,-1,24,.,28,51,85,0,194,2 +48,28.3,0,.,0,.,0,6.1,28,51,85,0,194,2 +48,28.3,1,700,0.5,.,0,.,28,51,85,0,194,2 +48,30,0,.,0,.,0,53.2,28,51,85,0,194,2 +48,35.2,0,.,0,.,0,29.6,28,51,85,0,194,2 +49,0,1,500,0.5,-1,24,.,20,44,60,0,170,1 +49,22.9,0,.,0,.,0,8.4,20,44,60,0,170,1 +49,23.1,1,500,0.5,.,0,.,20,44,60,0,170,1 +49,24.1,0,.,0,.,0,67.2,20,44,60,0,170,1 +49,29.6,0,.,0,.,0,30.5,20,44,60,0,170,1 +50,0,1,500,0.5,-1,24,.,60,51,68,1,131,1 +50,24.4,0,.,0,.,0,8.9,60,51,68,1,131,1 +50,24.4,1,500,0.5,.,0,.,60,51,68,1,131,1 +50,25.4,0,.,0,.,0,43.9,60,51,68,1,131,1 +50,26.4,0,.,0,.,0,23.3,60,51,68,1,131,1 +51,0,1,500,0.5,-1,24,.,73,126,85,1,56,1 +51,24,0,.,0,.,0,27.7,73,126,85,1,56,1 +51,28.1,1,500,0.5,.,0,.,73,126,85,1,56,1 +51,29.1,0,.,0,.,0,71.6,73,126,85,1,56,1 +51,32.5,0,.,0,.,0,50.5,73,126,85,1,56,1 +52,0,1,850,0.5,-1,24,.,68,47,115,0,183,1 +52,25.5,0,.,0,.,0,15.4,68,47,115,0,183,1 +52,25.6,1,850,0.5,.,0,.,68,47,115,0,183,1 +52,28.6,0,.,0,.,0,70.4,68,47,115,0,183,1 +52,32.6,0,.,0,.,0,50.5,68,47,115,0,183,1 +52,0,4,850,0.5,-1,24,.,68,58,115,0,148,2 +52,25,0,.,0,.,0,30.1,68,58,115,0,148,2 +52,25,1,850,0.5,.,0,.,68,58,115,0,148,2 +52,26.6,0,.,0,.,0,108.6,68,58,115,0,148,2 +52,31.7,0,.,0,.,0,71.4,68,58,115,0,148,2 +52,0,4,850,0.5,-1,24,.,68,52,118,0,170,3 +52,25,0,.,0,.,0,27.1,68,52,118,0,170,3 +52,25,1,850,0.5,.,0,.,68,52,118,0,170,3 +52,27,0,.,0,.,0,95.7,68,52,118,0,170,3 +52,31.5,0,.,0,.,0,61.7,68,52,118,0,170,3 +52,0,4,850,0.5,-1,24,.,68,47,115,0,183,4 +52,23.5,0,.,0,.,0,43.4,68,47,115,0,183,4 +52,23.5,1,850,0.5,.,0,.,68,47,115,0,183,4 +52,24.5,0,.,0,.,0,108.4,68,47,115,0,183,4 +52,29.5,0,.,0,.,0,73.9,68,47,115,0,183,4 +53,0,1,800,0.5,-1,24,.,64,70,93,1,124,1 +53,25.7,0,.,0,.,0,11.7,64,70,93,1,124,1 +53,25.7,1,800,0.5,.,0,.,64,70,93,1,124,1 +53,26.7,0,.,0,.,0,61,64,70,93,1,124,1 +53,30.3,0,.,0,.,0,36.4,64,70,93,1,124,1 +53,0,4,800,0.5,-1,24,.,65,64,102,1,147,2 +53,26,0,.,0,.,0,16.1,65,64,102,1,147,2 +53,26.1,1,800,0.5,.,0,.,65,64,102,1,147,2 +53,27.7,0,.,0,.,0,64.7,65,64,102,1,147,2 +53,33.25,0,.,0,.,0,42.6,65,64,102,1,147,2 +53,0,4,800,0.5,-1,24,.,65,74,105,1,131,3 +53,25.2,0,.,0,.,0,22,65,74,105,1,131,3 +53,25.3,1,800,0.5,.,0,.,65,74,105,1,131,3 +53,26.9,0,.,0,.,0,68.6,65,74,105,1,131,3 +53,31.7,0,.,0,.,0,45.8,65,74,105,1,131,3 +53,0,4,1000,0.5,-1,24,.,65,56,108,1,178,4 +53,25,0,.,0,.,0,21.4,65,56,108,1,178,4 +53,25.1,1,1000,0.5,.,0,.,65,56,108,1,178,4 +53,26.8,0,.,0,.,0,89.4,65,56,108,1,178,4 +53,31.75,0,.,0,.,0,49.9,65,56,108,1,178,4 +53,0,4,1000,0.5,-1,24,.,65,60,108,1,166,5 +53,26.3,0,.,0,.,0,11.9,65,60,108,1,166,5 +53,26.6,1,1000,0.5,.,0,.,65,60,108,1,166,5 +53,27.75,0,.,0,.,0,70.8,65,60,108,1,166,5 +53,32.3,0,.,0,.,0,43.1,65,60,108,1,166,5 +53,0,4,1000,0.5,-1,24,.,65,70,109,1,144,6 +53,25.7,0,.,0,.,0,12.6,65,70,109,1,144,6 +53,25.75,1,1000,0.5,.,0,.,65,70,109,1,144,6 +53,27.3,0,.,0,.,0,81.7,65,70,109,1,144,6 +53,32.8,0,.,0,.,0,46.5,65,70,109,1,144,6 +53,0,4,1000,0.5,-1,24,.,65,67,114.5,1,158,7 +53,26.5,0,.,0,.,0,10.2,65,67,114.5,1,158,7 +53,26.5,1,1000,0.5,.,0,.,65,67,114.5,1,158,7 +53,29,0,.,0,.,0,71.4,65,67,114.5,1,158,7 +53,33.1,0,.,0,.,0,51.3,65,67,114.5,1,158,7 +53,0,4,1000,0.5,-1,24,.,65,72,113,1,145,8 +53,27,0,.,0,.,0,13.9,65,72,113,1,145,8 +53,27.4,1,1000,0.5,.,0,.,65,72,113,1,145,8 +53,29,0,.,0,.,0,79.2,65,72,113,1,145,8 +53,33.3,0,.,0,.,0,54.4,65,72,113,1,145,8 +53,0,4,1000,0.5,-1,24,.,65,73,115,1,145,9 +53,24,0,.,0,.,0,22.5,65,73,115,1,145,9 +53,24.1,1,1000,0.5,.,0,.,65,73,115,1,145,9 +53,26,0,.,0,.,0,97.6,65,73,115,1,145,9 +53,29.3,0,.,0,.,0,66.7,65,73,115,1,145,9 +54,0,1,600,0.5,.,0,.,75,690,95,1,11,1 +54,47,0,.,0,.,0,15.8,75,690,95,1,11,1 +54,48,1,600,0.5,.,0,.,75,690,95,1,11,1 +54,96,1,600,0.5,.,0,.,75,249,95,1,31,1 +54,143,0,.,0,.,0,20.1,75,249,95,1,31,1 +54,144,1,600,0.5,.,0,.,75,249,95,1,31,1 +54,167.7,0,.,0,.,0,21.8,75,249,95,1,31,1 +54,192.5,0,.,0,.,0,26.6,75,249,95,1,31,1 +54,192.6,1,600,0.5,.,0,.,75,249,95,1,31,1 +54,195,0,.,0,.,0,77.9,75,249,95,1,31,1 +54,206,0,.,0,.,0,55.9,75,249,95,1,31,1 +54,240,1,700,0.5,.,0,.,75,249,95,1,31,1 +54,264,0,.,0,.,0,71.9,75,249,95,1,31,1 +54,290.2,1,700,0.5,.,0,.,75,195,89,1,36,1 +54,290.25,0,.,0,.,0,22.5,75,195,89,1,36,1 +54,290.26,1,700,0.5,.,0,.,75,195,89,1,36,1 +54,291.1,0,.,0,.,0,81.1,75,195,89,1,36,1 +54,296,0,.,0,.,0,57.9,75,195,89,1,36,1 +55,0,1,500,0.5,-1,24,.,29,40,59,0,170,1 +55,24,0,.,0,.,0,6.5,29,40,59,0,170,1 +55,25,1,500,0.5,.,0,.,29,40,59,0,170,1 +55,26.2,0,.,0,.,0,75.4,29,40,59,0,170,1 +55,31.6,0,.,0,.,0,37.7,29,40,59,0,170,1 +55,0,4,500,0.5,-1,24,.,29,41,61,0,172,3 +55,25.8,0,.,0,.,0,7.9,29,41,61,0,172,3 +55,25.9,1,500,0.5,.,0,.,29,41,61,0,172,3 +55,27.5,0,.,0,.,0,61.1,29,41,61,0,172,3 +55,32.2,0,.,0,.,0,33.7,29,41,61,0,172,3 +55,0,4,500,0.5,-1,24,.,29,41,61,0,172,2 +55,24,0,.,0,.,0,6.4,29,41,61,0,172,2 +55,24.2,1,500,0.5,.,0,.,29,41,61,0,172,2 +55,25.2,0,.,0,.,0,85.1,29,41,61,0,172,2 +55,31,0,.,0,.,0,33.6,29,41,61,0,172,2 +56,0,1,700,0.5,-1,24,.,45,139,94,1,79,1 +56,24.7,0,.,0,.,0,22.4,45,139,94,1,79,1 +56,24.8,1,700,0.5,.,0,.,45,139,94,1,79,1 +56,26.2,0,.,0,.,0,91.7,45,139,94,1,79,1 +56,31,0,.,0,.,0,58.7,45,139,94,1,79,1 +56,0,4,700,0.5,-1,24,.,45,124,98,1,92,2 +56,19.75,0,.,0,.,0,16.4,45,124,98,1,92,2 +56,19.8,1,700,0.5,.,0,.,45,124,98,1,92,2 +56,20.9,0,.,0,.,0,75.8,45,124,98,1,92,2 +56,25.4,0,.,0,.,0,47.3,45,124,98,1,92,2 +57,0,1,500,0.5,-1,24,.,84,94,76,0,47,1 +57,24.5,0,.,0,.,0,27.6,84,94,76,0,47,1 +57,24.5,1,500,0.5,.,0,.,84,94,76,0,47,1 +57,26,0,.,0,.,0,92.1,84,94,76,0,47,1 +57,31,0,.,0,.,0,69.8,84,94,76,0,47,1 +57,0,4,500,0.5,-1,24,.,84,78,76,0,57,2 +57,25.75,0,.,0,.,0,44.9,84,78,76,0,57,2 +57,25.75,1,500,0.5,.,0,.,84,78,76,0,57,2 +57,26.25,0,.,0,.,0,88.6,84,78,76,0,57,2 +57,32,0,.,0,.,0,73.8,84,78,76,0,57,2 +58,0,1,500,0.5,-1,24,.,65,69,72,0,81,1 +58,25,0,.,0,.,0,15.4,65,69,72,0,81,1 +58,25.1,1,500,0.5,.,0,.,65,69,72,0,81,1 +58,25.6,0,.,0,.,0,58.3,65,69,72,0,81,1 +58,30,0,.,0,.,0,39.8,65,69,72,0,81,1 +59,0,1,500,0.5,-1,24,.,19,49,66,1,200,1 +59,24,0,.,0,.,0,11.7,19,49,66,1,200,1 +59,25,1,500,0.5,.,0,.,19,49,66,1,200,1 +59,27,0,.,0,.,0,54.6,19,49,66,1,200,1 +59,31.5,0,.,0,.,0,34.1,19,49,66,1,200,1 +60,0,1,700,0.5,-1,24,.,74,60,72,1,97,1 +60,24.7,0,.,0,.,0,23,74,60,72,1,97,1 +60,24.8,1,700,0.5,.,0,.,74,60,72,1,97,1 +60,26,0,.,0,.,0,67.8,74,60,72,1,97,1 +60,31.3,0,.,0,.,0,47.2,74,60,72,1,97,1 +60,0,4,700,0.5,-1,24,.,74,52,72,1,112,2 +60,24,0,.,0,.,0,20.6,74,52,72,1,112,2 +60,24,1,700,0.5,.,0,.,74,52,72,1,112,2 +60,25.5,0,.,0,.,0,54.4,74,52,72,1,112,2 +60,30,0,.,0,.,0,41.9,74,52,72,1,112,2 +60,0,4,700,0.5,-1,24,.,74,58,72,1,101,3 +60,24,0,.,0,.,0,25.4,74,58,72,1,101,3 +60,24.1,1,700,0.5,.,0,.,74,58,72,1,101,3 +60,25.2,0,.,0,.,0,62.22,74,58,72,1,101,3 +60,30.2,0,.,0,.,0,37.2,74,58,72,1,101,3 +61,0,1,500,0.5,-1,24,.,60,40,60,1,148,1 +61,25.75,0,.,0,.,0,18,60,40,60,1,148,1 +61,26,1,500,0.5,.,0,.,60,40,60,1,148,1 +61,27.15,0,.,0,.,0,70.7,60,40,60,1,148,1 +61,31.3,0,.,0,.,0,41.6,60,40,60,1,148,1 +61,0,4,500,0.5,-1,24,.,60,40,60,1,148,2 +61,25.5,0,.,0,.,0,9.6,60,40,60,1,148,2 +61,25.75,1,500,0.5,.,0,.,60,40,60,1,148,2 +61,26.75,0,.,0,.,0,57.1,60,40,60,1,148,2 +61,31.2,0,.,0,.,0,29.7,60,40,60,1,148,2 +62,0,1,700,0.5,-1,24,.,42,65,85,1,158,1 +62,25,0,.,0,.,0,16.8,42,65,85,1,158,1 +62,25.2,1,700,0.5,.,0,.,42,65,85,1,158,1 +62,26.2,0,.,0,.,0,88.1,42,65,85,1,158,1 +62,31.7,0,.,0,.,0,42.5,42,65,85,1,158,1 +63,0,1,500,0.5,-1,24,.,59,76,63,1,83,1 +63,24.5,0,.,0,.,0,8,59,76,63,1,83,1 +63,26.2,1,500,0.5,.,0,.,59,76,63,1,83,1 +63,27.1,0,.,0,.,0,48.1,59,76,63,1,83,1 +63,30.75,0,.,0,.,0,35.4,59,76,63,1,83,1 +63,0,4,500,0.5,-1,24,.,59,74,70,1,94,2 +63,24.7,0,.,0,.,0,7.7,59,74,70,1,94,2 +63,24.75,1,500,0.5,.,0,.,59,74,70,1,94,2 +63,25.75,0,.,0,.,0,64.4,59,74,70,1,94,2 +63,31.25,0,.,0,.,0,40,59,74,70,1,94,2 +63,0,4,700,0.5,-1,24,.,59,87,67,1,77,4 +63,24,0,.,0,.,0,20.3,59,87,67,1,77,4 +63,24.2,1,700,0.5,.,0,.,59,87,67,1,77,4 +63,24.7,0,.,0,.,0,100.7,59,87,67,1,77,4 +63,0,4,700,0.5,-1,24,.,59,84,65,1,77,3 +63,25,0,.,0,.,0,14.4,59,84,65,1,77,3 +63,25.25,1,700,0.5,.,0,.,59,84,65,1,77,3 +63,26.4,0,.,0,.,0,83,59,84,65,1,77,3 +63,31.75,0,.,0,.,0,61,59,84,65,1,77,3 +63,0,4,750,0.5,-1,24,.,59,82,65,1,79,5 +63,24.3,0,.,0,.,0,30,59,82,65,1,79,5 +63,25.9,1,750,0.5,.,0,.,59,82,65,1,79,5 +63,26.1,0,.,0,.,0,92.2,59,82,65,1,79,5 +63,31.25,0,.,0,.,0,57.2,59,82,65,1,79,5 +63,0,4,700,0.5,-1,24,.,60,73,65,1,88,8 +63,24.5,0,.,0,.,0,14.5,60,73,65,1,88,8 +63,24.75,1,700,0.5,.,0,.,60,73,65,1,88,8 +63,26.1,0,.,0,.,0,63.1,60,73,65,1,88,8 +63,31.9,0,.,0,.,0,36.5,60,73,65,1,88,8 +63,0,4,700,0.5,-1,24,.,59,89,65,1,73,6 +63,24,0,.,0,.,0,20.2,59,89,65,1,73,6 +63,0,4,700,0.5,-1,24,.,59,83,65,1,78,7 +63,24,0,.,0,.,0,20.8,59,83,65,1,78,7 +63,24,1,700,0.5,.,0,.,59,83,65,1,78,7 +63,25,0,.,0,.,0,116.8,59,83,65,1,78,7 +64,0,1,500,0.5,-1,24,.,57,86,63,1,75,1 +64,27.1,0,.,0,.,0,9.1,57,86,63,1,75,1 +64,27.1,1,500,0.5,.,0,.,57,86,63,1,75,1 +64,28.1,0,.,0,.,0,83,57,86,63,1,75,1 +64,36.7,0,.,0,.,0,43.4,57,86,63,1,75,1 +65,0,1,350,0.5,-1,24,.,27,41,37,1,125,1 +65,24,0,.,0,.,0,2.8,27,41,37,1,125,1 +65,24.1,1,350,0.5,.,0,.,27,41,37,1,125,1 +65,25.9,0,.,0,.,0,54.5,27,41,37,1,125,1 +65,30.4,0,.,0,.,0,26.5,27,41,37,1,125,1 +65,0,4,350,0.5,-1,24,.,27,75,37,1,69,2 +65,24.5,0,.,0,.,0,11.4,27,75,37,1,69,2 +65,24.7,1,350,0.5,.,0,.,27,75,37,1,69,2 +65,25.8,0,.,0,.,0,73.2,27,75,37,1,69,2 +65,30.3,0,.,0,.,0,41.5,27,75,37,1,69,2 +65,0,4,350,0.5,-1,24,.,27,103,39.5,1,53,3 +65,26,0,.,0,.,0,6.5,27,103,39.5,1,53,3 +65,26.1,1,350,0.5,.,0,.,27,103,39.5,1,53,3 +65,27.25,0,.,0,.,0,66.3,27,103,39.5,1,53,3 +65,32.7,0,.,0,.,0,41.6,27,103,39.5,1,53,3 +65,0,4,350,0.5,-1,24,.,27,85,39.5,1,65,4 +65,26.5,0,.,0,.,0,7.4,27,85,39.5,1,65,4 +65,27,1,350,0.5,.,0,.,27,85,39.5,1,65,4 +65,28.4,0,.,0,.,0,63.6,27,85,39.5,1,65,4 +65,0,4,500,0.5,-1,24,.,27,47,37.5,1,111,5 +65,22.2,0,.,0,.,0,6.4,27,47,37.5,1,111,5 +65,22.25,1,500,0.5,.,0,.,27,47,37.5,1,111,5 +65,23.8,0,.,0,.,0,76.5,27,47,37.5,1,111,5 +65,28.5,0,.,0,.,0,34.1,27,47,37.5,1,111,5 +65,0,4,350,0.5,-1,24,.,27,37,37,1,139,6 +65,25,0,.,0,.,0,4.7,27,37,37,1,139,6 +65,25,1,350,0.5,.,0,.,27,37,37,1,139,6 +65,26.5,0,.,0,.,0,46.8,27,37,37,1,139,6 +65,31,0,.,0,.,0,31,27,37,37,1,139,6 +66,0,1,500,0.5,-1,24,.,66,40,37,0,71,1 +66,24.5,0,.,0,.,0,26.9,66,40,37,0,71,1 +66,24.5,1,500,0.5,.,0,.,66,40,37,0,71,1 +66,26.4,0,.,0,.,0,58.1,66,40,37,0,71,1 +66,30.9,0,.,0,.,0,42.5,66,40,37,0,71,1 +67,0,1,700,0.5,.,24,.,55,46,82,1,186,1 +67,24,0,.,0,.,0,13,55,46,82,1,186,1 +67,24,1,700,0.5,.,0,.,55,46,82,1,186,1 +67,25.5,0,.,0,.,0,66.3,55,46,82,1,186,1 +67,30,0,.,0,.,0,39.5,55,46,82,1,186,1 +68,0,1,500,0.5,-1,48,.,75,153,87,1,45,1 +68,48,0,.,0,.,0,8,75,153,87,1,45,1 +68,48.1,1,500,0.5,.,0,.,75,153,87,1,45,1 +68,49.5,0,.,0,.,0,72.2,75,153,87,1,45,1 +68,51,0,.,0,.,0,60.3,75,153,87,1,45,1 +68,0,4,500,0.5,-1,48,.,75,139,87,1,50,2 +68,48,0,.,0,.,0,18,75,139,87,1,50,2 +68,0,4,500,0.5,.,48,.,75,139,87,1,50,3 +68,48,0,.,0,.,0,11,75,139,87,1,50,3 +68,48,1,500,0.5,.,0,.,75,139,87,1,50,3 +68,49.5,0,.,0,-1,0,63.5,75,139,87,1,50,3 +68,51.5,0,.,0,.,0,52.7,75,139,87,1,50,3 +68,0,4,500,0.5,-1,48,.,75,140,81,1,46,4 +68,47,0,.,0,.,0,33.7,75,140,81,1,46,4 +68,47.1,1,500,0.5,.,0,.,75,140,81,1,46,4 +68,49.5,0,.,0,.,0,96.2,75,140,81,1,46,4 +68,54.75,0,.,0,.,0,70.6,75,140,81,1,46,4 +68,0,4,500,0.5,-1,48,.,75,155,81,1,42,5 +68,48.1,0,.,0,.,0,11.2,75,155,81,1,42,5 +68,48.1,1,500,0.5,.,0,.,75,155,81,1,42,5 +68,49,0,.,0,.,0,54.5,75,155,81,1,42,5 +68,54,0,.,0,.,0,39.1,75,155,81,1,42,5 +68,0,4,500,0.5,.,48,.,75,143,82,1,46,6 +68,47.8,0,.,0,.,0,19,75,143,82,1,46,6 +68,0,4,500,0.5,.,48,.,76,141,87,1,49,7 +68,48,0,.,0,.,0,7.4,76,141,87,1,49,7 +68,48.1,1,500,0.5,.,0,.,76,141,87,1,49,7 +68,49.1,0,.,0,-1,0,56.4,76,141,87,1,49,7 +68,53.6,0,.,0,.,0,43.3,76,141,87,1,49,7 +69,0,1,800,0.5,-1,24,.,27,94,84.5,1,125,1 +69,25.5,0,.,0,.,0,41.9,27,94,84.5,1,125,1 +69,25.6,1,800,0.5,.,0,.,27,94,84.5,1,125,1 +69,28.25,0,.,0,.,0,77.6,27,94,84.5,1,125,1 +69,31.25,0,.,0,.,0,69.9,27,94,84.5,1,125,1 +70,0,1,350,0.5,-1,24,.,74,53,58.6,0,76,1 +70,23.5,0,.,0,.,0,20.8,74,53,58.6,0,76,1 +70,23.5,1,350,0.5,.,0,.,74,53,58.6,0,76,1 +70,24.5,0,.,0,.,0,53.2,74,53,58.6,0,76,1 +70,29,0,.,0,.,0,34.9,74,53,58.6,0,76,1 +71,0,1,850,0.5,-1,24,.,67,64,97,0,115,1 +71,26.1,0,.,0,.,0,20.5,67,64,97,0,115,1 +71,26.1,1,850,0.5,.,0,.,67,64,97,0,115,1 +71,27.4,0,.,0,.,0,106.1,67,64,97,0,115,1 +71,30.5,0,.,0,.,0,61.4,67,64,97,0,115,1 +72,0,1,500,0.5,-1,24,.,86,91,75,1,55,1 +72,25,0,.,0,.,0,35.2,86,91,75,1,55,1 +72,25,1,500,0.5,.,0,.,86,91,75,1,55,1 +72,26,0,.,0,.,0,66.9,86,91,75,1,55,1 +72,30.8,0,.,0,.,0,52.5,86,91,75,1,55,1 +72,0,4,500,0.5,-1,24,.,86,101,68,1,45,2 +72,24.5,0,.,0,.,0,36.7,86,101,68,1,45,2 +72,24.5,1,500,0.5,.,0,.,86,101,68,1,45,2 +72,25.7,0,.,0,.,0,84.7,86,101,68,1,45,2 +72,29.3,0,.,0,.,0,73.7,86,101,68,1,45,2 +73,0,1,850,0.5,-1,24,.,62,95,99,1,100,1 +73,24,0,.,0,.,0,31.6,62,95,99,1,100,1 +73,24.5,1,850,0.5,.,0,.,62,95,99,1,100,1 +73,26,0,.,0,.,0,90,62,95,99,1,100,1 +73,31.25,0,.,0,.,0,64.2,62,95,99,1,100,1 +73,0,4,850,0.5,-1,24,.,62,72,99,1,132,2 +73,26,0,.,0,.,0,16.9,62,72,99,1,132,2 +73,26,1,850,0.5,.,0,.,62,72,99,1,132,2 +73,27.8,0,.,0,.,0,73.1,62,72,99,1,132,2 +73,30.5,0,.,0,.,0,47.4,62,72,99,1,132,2 +73,0,4,850,0.5,-1,24,.,62,89,105,1,113,3 +73,15.6,0,.,0,.,0,7.7,62,89,105,1,113,3 +73,15.6,1,850,0.5,.,0,.,62,89,105,1,113,3 +73,16.1,0,.,0,.,0,69.2,62,89,105,1,113,3 +73,22.7,0,.,0,.,0,39.3,62,89,105,1,113,3 +73,0,4,1000,0.5,-1,24,.,62,108,101,1,90,4 +73,19.5,0,.,0,.,0,10.5,62,108,101,1,90,4 +73,0,4,850,0.5,-1,24,.,62,104,101,1,93,5 +73,25,0,.,0,.,0,13.1,62,104,101,1,93,5 +73,25,1,850,0.5,.,0,.,62,104,101,1,93,5 +73,26.5,0,.,0,.,0,91.2,62,104,101,1,93,5 +73,31,0,.,0,.,0,56.9,62,104,101,1,93,5 +73,0,4,850,0.5,-1,24,.,62,93,106,1,109,6 +73,26.3,0,.,0,.,0,15.4,62,93,106,1,109,6 +73,26.5,1,850,0.5,.,0,.,62,93,106,1,109,6 +73,27.5,0,.,0,.,0,91.3,62,93,106,1,109,6 +73,32,0,.,0,.,0,63.5,62,93,106,1,109,6 +73,0,4,700,0.5,-1,24,.,65,90,118,1,121,7 +73,26.1,0,.,0,.,0,26.3,65,90,118,1,121,7 +73,26.3,1,700,0.5,.,0,.,65,90,118,1,121,7 +73,27.5,0,.,0,.,0,65,65,90,118,1,121,7 +73,31.8,0,.,0,.,0,46.8,65,90,118,1,121,7 +73,0,4,350,0.5,-1,24,.,65,86,113,1,121,8 +73,24.3,0,.,0,.,0,19.1,65,86,113,1,121,8 +73,25.6,1,350,0.5,.,0,.,65,86,113,1,121,8 +73,27.25,0,.,0,.,0,47.8,65,86,113,1,121,8 +74,0,1,700,0.5,-1,24,.,47,54,85,1,180,1 +74,24,1,700,0.5,.,0,.,47,54,85,1,180,1 +74,27.7,0,.,0,.,0,46.6,47,54,85,1,180,1 +74,29.8,0,.,0,.,0,38.2,47,54,85,1,180,1 +74,0,4,700,0.5,-1,24,.,47,56,87,1,178,2 +74,26.5,0,.,0,.,0,4.4,47,56,87,1,178,2 +74,26.5,1,700,0.5,.,0,.,47,56,87,1,178,2 +74,28,0,.,0,.,0,55.7,47,56,87,1,178,2 +74,34,0,.,0,.,0,26.7,47,56,87,1,178,2 +74,0,4,700,0.5,-1,24,.,47,56,85,1,174,3 +74,23.6,0,.,0,.,0,6.9,47,56,85,1,174,3 +74,23.7,1,700,0.5,.,0,.,47,56,85,1,174,3 +74,24.8,0,.,0,.,0,80.6,47,56,85,1,174,3 +74,30.2,0,.,0,.,0,38.1,47,56,85,1,174,3 +74,0,4,700,0.5,-1,24,.,47,62,86,1,159,4 +74,23.7,0,.,0,.,0,8,47,62,86,1,159,4 +74,23.75,1,700,0.5,.,0,.,47,62,86,1,159,4 +74,25.9,0,.,0,.,0,78.6,47,62,86,1,159,4 +74,29.6,0,.,0,.,0,41.8,47,62,86,1,159,4 +75,0,1,700,0.5,-1,24,.,78,60,80,0,86,1 +75,23.1,0,.,0,.,0,46.1,78,60,80,0,86,1 +75,23.1,1,700,0.5,.,0,.,78,60,80,0,86,1 +75,25.1,0,.,0,.,0,138.4,78,60,80,0,86,1 +75,29.3,0,.,0,.,0,110.1,78,60,80,0,86,1 +76,0,1,700,0.5,-1,24,.,83,76,83,0,65,1 +76,24.3,0,.,0,.,0,32.1,83,76,83,0,65,1 +76,24.4,1,700,0.5,.,0,.,83,76,83,0,65,1 +76,25.7,0,.,0,.,0,85.7,83,76,83,0,65,1 +76,31.1,0,.,0,.,0,61.1,83,76,83,0,65,1 +76,0,4,500,0.5,-1,24,.,83,72,83,0,68,2 +76,26.6,0,.,0,.,0,25.4,83,72,83,0,68,2 +76,26.7,1,500,0.5,.,0,.,83,72,83,0,68,2 +76,28.1,0,.,0,.,0,25.4,83,72,83,0,68,2 +76,33.3,0,.,0,.,0,56.1,83,72,83,0,68,2 +76,0,4,500,0.5,-1,24,.,83,77,77,0,59,3 +76,29,0,.,0,.,0,16.2,83,77,77,0,59,3 +76,29.2,1,500,0.5,.,0,.,83,77,77,0,59,3 +76,30.25,0,.,0,.,0,83.4,83,77,77,0,59,3 +76,34,0,.,0,.,0,59.9,83,77,77,0,59,3 +76,0,4,500,0.5,-1,24,.,83,69,77,0,66,4 +76,27.4,0,.,0,.,0,33.2,83,69,77,0,66,4 +76,27.4,1,500,0.5,.,0,.,83,69,77,0,66,4 +76,29.3,0,.,0,.,0,83.7,83,69,77,0,66,4 +77,0,1,700,0.5,-1,24,.,45,108,91,1,98,1 +77,23.5,0,.,0,.,0,20.5,45,108,91,1,98,1 +77,24.1,1,700,0.5,.,0,.,45,108,91,1,98,1 +77,25.1,0,.,0,.,0,67.9,45,108,91,1,98,1 +77,30.1,0,.,0,.,0,47.2,45,108,91,1,98,1 +78,0,1,700,0.5,-1,24,.,19,44,71,0,203,1 +78,25.5,0,.,0,.,0,6.9,19,44,71,0,203,1 +78,26,1,700,0.5,.,0,.,19,44,71,0,203,1 +78,27.3,0,.,0,.,0,70.4,19,44,71,0,203,1 +78,32.7,0,.,0,.,0,40.5,19,44,71,0,203,1 +79,0,1,850,0.5,.,24,.,52,86,108,1,136,1 +79,25.1,0,.,0,.,0,15.7,52,86,108,1,136,1 +79,26.2,1,850,0.5,.,0,.,52,86,108,1,136,1 +79,27.25,0,.,0,.,0,66.3,52,86,108,1,136,1 +79,32.75,0,.,0,.,0,41.4,52,86,108,1,136,1 +79,0,4,850,0.5,-1,24,.,52,75,118,1,170,2 +79,25.6,0,.,0,.,0,10,52,75,118,1,170,2 +79,26,1,850,0.5,.,0,.,52,75,118,1,170,2 +79,27.1,0,.,0,.,0,67.5,52,75,118,1,170,2 +79,32.1,0,.,0,.,0,43.2,52,75,118,1,170,2 +80,0,1,700,0.5,.,0,.,69,403,106,1,23,1 +80,48,1,700,0.5,.,0,.,69,403,106,1,23,1 +80,93.4,0,.,0,.,0,9.1,69,403,106,1,23,1 +80,93.6,1,700,0.5,.,0,.,69,403,106,1,23,1 +80,94.75,0,.,0,.,0,33.4,69,403,106,1,23,1 +80,100,0,.,0,.,0,26.4,69,403,106,1,23,1 +80,0,4,700,0.5,.,0,.,69,351,106,1,26,2 +80,48,1,700,0.5,.,0,.,69,351,106,1,26,2 +80,96,0,.,0,.,0,18.7,69,351,106,1,26,2 +80,96,1,700,0.5,.,0,.,69,351,106,1,26,2 +80,97.7,0,.,0,.,0,64.4,69,351,106,1,26,2 +81,0,1,350,0.5,-1,24,.,66,39,50,1,117,1 +81,23.9,0,.,0,.,0,12.5,66,39,50,1,117,1 +81,24,1,350,0.5,.,0,.,66,39,50,1,117,1 +81,25,0,.,0,.,0,46.7,66,39,50,1,117,1 +81,29.5,0,.,0,.,0,28.3,66,39,50,1,117,1 +81,0,4,350,0.5,-1,24,.,67,48,50,1,94,2 +81,25.7,0,.,0,.,0,11.2,67,48,50,1,94,2 +81,25.75,1,350,0.5,.,0,.,67,48,50,1,94,2 +81,26.8,0,.,0,.,0,53,67,48,50,1,94,2 +81,32.5,0,.,0,.,0,26.4,67,48,50,1,94,2 +81,0,4,350,0.5,-1,24,.,67,53,43,1,73,3 +81,26.9,0,.,0,.,0,8.8,67,53,43,1,73,3 +81,27,1,350,0.5,.,0,.,67,53,43,1,73,3 +81,28.3,0,.,0,.,0,52.7,67,53,43,1,73,3 +81,33.8,0,.,0,.,0,30.2,67,53,43,1,73,3 +81,0,4,350,0.5,-1,24,.,67,42,47,1,100,4 +81,25.8,0,.,0,.,0,9.5,67,42,47,1,100,4 +81,25.8,1,350,0.5,.,0,.,67,42,47,1,100,4 +81,27.6,0,.,0,.,0,47.5,67,42,47,1,100,4 +81,32.1,0,.,0,.,0,29.1,67,42,47,1,100,4 +82,0,1,700,0.5,-1,24,.,75,66,87,1,105,1 +82,24,0,.,0,.,0,13.4,75,66,87,1,105,1 +82,24,1,700,0.5,.,0,.,75,66,87,1,105,1 +82,25.5,0,.,0,.,0,51.9,75,66,87,1,105,1 +82,31,0,.,0,.,0,31.6,75,66,87,1,105,1 +82,0,4,700,0.5,-1,24,.,75,62,85,1,110,2 +82,24.25,0,.,0,.,0,16.8,75,62,85,1,110,2 +82,24.25,1,700,0.5,.,0,.,75,62,85,1,110,2 +82,26,0,.,0,.,0,62.5,75,62,85,1,110,2 +82,30.5,0,.,0,.,0,38.8,75,62,85,1,110,2 +82,0,4,700,0.5,-1,24,.,75,85,85,1,80,3 +82,26,0,.,0,.,0,21.9,75,85,85,1,80,3 +82,26.25,1,700,0.5,.,0,.,75,85,85,1,80,3 +82,27.25,0,.,0,.,0,82.5,75,85,85,1,80,3 +82,32.75,0,.,0,.,0,22.1,75,85,85,1,80,3 +82,0,4,700,0.5,-1,24,.,75,68,85,1,100,4 +82,26.9,0,.,0,.,0,19.8,75,68,85,1,100,4 +82,27,1,700,0.5,.,0,.,75,68,85,1,100,4 +82,29.5,0,.,0,.,0,59.7,75,68,85,1,100,4 +82,33,0,.,0,.,0,39.7,75,68,85,1,100,4 +82,0,4,700,0.5,-1,24,.,75,65,85,1,105,5 +82,21.1,0,.,0,.,0,50.6,75,65,85,1,105,5 +82,21.1,1,700,0.5,.,0,.,75,65,85,1,105,5 +82,22.1,0,.,0,.,0,93.1,75,65,85,1,105,5 +82,27.1,0,.,0,.,0,21.4,75,65,85,1,105,5 +83,0,1,700,0.5,-1,24,.,53,60,65,0,98,1 +83,25,0,.,0,.,0,22.3,53,60,65,0,98,1 +83,25,1,700,0.5,.,0,.,53,60,65,0,98,1 +83,26.5,0,.,0,.,0,62.5,53,60,65,0,98,1 +83,0,4,700,0.5,-1,24,.,54,52,121,0,208,5 +83,24,0,.,0,.,0,17.6,54,52,121,0,208,5 +83,24,1,700,0.5,.,0,.,54,52,121,0,208,5 +83,25.5,0,.,0,.,0,79.3,54,52,121,0,208,5 +83,30.1,0,.,0,.,0,48,54,52,121,0,208,5 +83,0,4,700,0.5,-1,24,.,53,54,120,0,201,2 +83,24,0,.,0,.,0,24.5,53,54,120,0,201,2 +83,24,1,700,0.5,.,0,.,53,54,120,0,201,2 +83,25.2,0,.,0,.,0,90.3,53,54,120,0,201,2 +83,31.6,0,.,0,.,0,54.9,53,54,120,0,201,2 +83,0,4,700,0.5,-1,24,.,53,56,116,0,187,3 +83,24.4,0,.,0,.,0,10,53,56,116,0,187,3 +83,25.6,1,700,0.5,.,0,.,53,56,116,0,187,3 +83,26.8,0,.,0,.,0,60.9,53,56,116,0,187,3 +83,31.4,0,.,0,.,0,40.1,53,56,116,0,187,3 +83,0,4,700,0.5,-1,24,.,53,66,130,0,178,4 +83,25.25,0,.,0,.,0,16.1,53,66,130,0,178,4 +83,25.7,1,700,0.5,.,0,.,53,66,130,0,178,4 +83,26.3,0,.,0,.,0,87.1,53,66,130,0,178,4 +83,31.8,0,.,0,.,0,47.8,53,66,130,0,178,4 +83,0,4,700,0.5,-1,24,.,54,64,121,0,169,6 +83,25.25,0,.,0,.,0,17.6,54,64,121,0,169,6 +83,25.75,1,700,0.5,.,0,.,54,64,121,0,169,6 +83,31.6,0,.,0,.,0,73.6,54,64,121,0,169,6 +83,33,0,.,0,.,0,60.5,54,64,121,0,169,6 +83,0,4,700,0.5,-1,24,.,54,59,115,0,174,7 +83,24.75,0,.,0,.,0,15.4,54,59,115,0,174,7 +83,25.1,1,700,0.5,.,0,.,54,59,115,0,174,7 +83,25.7,0,.,0,.,0,90.8,54,59,115,0,174,7 +83,31,0,.,0,.,0,52.1,54,59,115,0,174,7 +84,0,1,500,0.5,-1,24,.,21,46,51,0,137,1 +84,22.1,0,.,0,.,0,15.2,21,46,51,0,137,1 +84,22.2,1,500,0.5,.,0,.,21,46,51,0,137,1 +84,23.8,0,.,0,.,0,79.8,21,46,51,0,137,1 +84,0,4,500,0.5,-1,24,.,21,50,52,0,129,2 +84,24.25,0,.,0,.,0,10,21,50,52,0,129,2 +84,24.25,1,500,0.5,.,0,.,21,50,52,0,129,2 +84,25.1,0,.,0,.,0,77.8,21,50,52,0,129,2 +84,29.75,0,.,0,.,0,40.2,21,50,52,0,129,2 +85,0,1,500,0.5,-1,48,.,72,94,104,0,78,1 +85,47.25,0,.,0,.,0,25.3,72,94,104,0,78,1 +85,47.8,1,500,0.5,.,0,.,72,94,104,0,78,1 +85,49.25,0,.,0,.,0,56.5,72,94,104,0,78,1 +85,53.3,0,.,0,.,0,41.1,72,94,104,0,78,1 +85,0,4,850,0.5,-1,48,.,72,102,107,0,74,2 +85,50,0,.,0,.,0,22,72,102,107,0,74,2 +85,50,1,850,0.5,.,0,.,72,102,107,0,74,2 +85,51.2,0,.,0,.,0,53.4,72,102,107,0,74,2 +85,56,0,.,0,.,0,38.1,72,102,107,0,74,2 +85,0,4,850,0.5,-1,48,.,72,101,98,0,69,3 +85,48,0,.,0,.,0,14.5,72,101,98,0,69,3 +85,48,1,850,0.5,.,0,.,72,101,98,0,69,3 +85,49,0,.,0,.,0,79.4,72,101,98,0,69,3 +85,54,0,.,0,.,0,59.3,72,101,98,0,69,3 +85,0,4,850,0.5,-1,48,.,72,122,106,0,61,4 +85,49.5,0,.,0,.,0,13.5,72,122,106,0,61,4 +85,49.9,1,850,0.5,.,0,.,72,122,106,0,61,4 +85,52.25,0,.,0,.,0,52.9,72,122,106,0,61,4 +85,56.5,0,.,0,.,0,38.6,72,122,106,0,61,4 +85,0,4,850,0.5,-1,48,.,72,146,97,0,47,5 +85,48,0,.,0,.,0,9.2,72,146,97,0,47,5 +85,48,1,850,0.5,.,0,.,72,146,97,0,47,5 +85,49,0,.,0,.,0,91.9,72,146,97,0,47,5 +85,54.5,0,.,0,.,0,62.5,72,146,97,0,47,5 +85,0,4,850,0.5,-1,48,.,72,155,103,0,47,6 +85,47.25,0,.,0,.,0,16.2,72,155,103,0,47,6 +85,47.3,1,850,0.5,.,0,.,72,155,103,0,47,6 +85,48.3,0,.,0,.,0,79.5,72,155,103,0,47,6 +85,54.3,0,.,0,.,0,53.1,72,155,103,0,47,6 +85,0,4,850,0.5,-1,48,.,72,83,102,0,87,7 +85,47.5,0,.,0,.,0,14,72,83,102,0,87,7 +85,47.5,1,850,0.5,.,0,.,72,83,102,0,87,7 +85,48.5,0,.,0,.,0,93.5,72,83,102,0,87,7 +85,49.3,0,.,0,.,0,32,72,83,102,0,87,7 +85,0,4,850,0.5,-1,48,.,72,114,98.5,0,61,9 +85,47,0,.,0,.,0,7.1,72,114,98.5,0,61,9 +85,47.25,1,850,0.5,.,0,.,72,114,98.5,0,61,9 +85,49.1,0,.,0,.,0,62.4,72,114,98.5,0,61,9 +85,52,0,.,0,.,0,50.8,72,114,98.5,0,61,9 +85,0,4,850,0.5,-1,48,.,72,162,93,0,41,8 +85,48.8,0,.,0,.,0,11.4,72,162,93,0,41,8 +85,49.6,1,850,0.5,.,0,.,72,162,93,0,41,8 +85,50.6,0,.,0,.,0,96.2,72,162,93,0,41,8 +85,55.7,0,.,0,.,0,83.1,72,162,93,0,41,8 +85,0,4,850,0.5,-1,48,.,72,170,104,0,43,10 +85,48.75,0,.,0,.,0,14.1,72,170,104,0,43,10 +85,48.8,1,850,0.5,.,0,.,72,170,104,0,43,10 +85,50,0,.,0,.,0,100.5,72,170,104,0,43,10 +85,55.3,0,.,0,.,0,64.6,72,170,104,0,43,10 diff --git a/man/PM_cov.Rd b/man/PM_cov.Rd index 896f47891..d91158d97 100755 --- a/man/PM_cov.Rd +++ b/man/PM_cov.Rd @@ -36,9 +36,9 @@ parameter, covariate-time, or parameter-time relationships. Michael Neely, Julian Otalvaro } \section{Public fields}{ - \if{html}{\out{
}} - \describe{ - \item{\code{data}}{A data frame with the following columns +\if{html}{\out{
}} +\describe{ +\item{\code{data}}{A data frame with the following columns \itemize{ \item id Subject identification \item time Times of covariate observations @@ -48,153 +48,148 @@ for each subject, replicated as necessary for covariate observation times and du parameter means and medians \item icen The type of summarized Bayesian posterior individual parameter values: mean or median }} - } - \if{html}{\out{
}} +} +\if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ - \itemize{ - \item \href{#method-PM_cov-initialize}{\code{PM_cov$new()}} - \item \href{#method-PM_cov-step}{\code{PM_cov$step()}} - \item \href{#method-PM_cov-summary}{\code{PM_cov$summary()}} - \item \href{#method-PM_cov-plot}{\code{PM_cov$plot()}} - \item \href{#method-PM_cov-print}{\code{PM_cov$print()}} - \item \href{#method-PM_cov-clone}{\code{PM_cov$clone()}} - } +\itemize{ +\item \href{#method-PM_cov-new}{\code{PM_cov$new()}} +\item \href{#method-PM_cov-step}{\code{PM_cov$step()}} +\item \href{#method-PM_cov-summary}{\code{PM_cov$summary()}} +\item \href{#method-PM_cov-plot}{\code{PM_cov$plot()}} +\item \href{#method-PM_cov-print}{\code{PM_cov$print()}} +\item \href{#method-PM_cov-clone}{\code{PM_cov$clone()}} +} } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_cov-initialize}{}}} -\subsection{\code{PM_cov$new()}}{ - Create new object populated with covariate-parameter information - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_cov$new(PMdata = NULL, path = ".", ...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{PMdata}}{include Saved, parsed output of prior run, used when source files are not available. +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_cov-new}{}}} +\subsection{Method \code{new()}}{ +Create new object populated with covariate-parameter information +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_cov$new(PMdata = NULL, path = ".", ...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{PMdata}}{include Saved, parsed output of prior run, used when source files are not available. .} - \item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. + +\item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. .} - \item{\code{...}}{Not currently used.} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - Creation of new \code{PM_cov} object is automatic and not generally necessary + +\item{\code{...}}{Not currently used.} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +Creation of new \code{PM_cov} object is automatic and not generally necessary for the user to do. - } } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_cov-step}{}}} -\subsection{\code{PM_cov$step()}}{ - Stepwise linear regression of covariates and Bayesian posterior +\subsection{Method \code{step()}}{ +Stepwise linear regression of covariates and Bayesian posterior parameter values - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_cov$step(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{PM_step}} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - See \link{PM_step}. - } +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_cov$step(...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{PM_step}} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +See \link{PM_step}. } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_cov-summary}{}}} -\subsection{\code{PM_cov$summary()}}{ - Summary method - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_cov$summary(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{summary.PM_cov}} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - See \link{summary.PM_cov}. - } +\subsection{Method \code{summary()}}{ +Summary method +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_cov$summary(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{summary.PM_cov}} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +See \link{summary.PM_cov}. +} + +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_cov-plot}{}}} -\subsection{\code{PM_cov$plot()}}{ - Plot method - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_cov$plot(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{plot.PM_cov}} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - See \link{plot.PM_cov}. - } +\subsection{Method \code{plot()}}{ +Plot method +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_cov$plot(...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{plot.PM_cov}} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +See \link{plot.PM_cov}. } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_cov-print}{}}} -\subsection{\code{PM_cov$print()}}{ - Print method - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_cov$print(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{print}} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - Print method for \link{PM_cov} - } +\subsection{Method \code{print()}}{ +Print method +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_cov$print(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{print}} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +Print method for \link{PM_cov} +} + +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_cov-clone}{}}} -\subsection{\code{PM_cov$clone()}}{ - The objects of this class are cloneable with this method. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_cov$clone(deep = FALSE)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{deep}}{Whether to make a deep clone.} - } - \if{html}{\out{
}} - } +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_cov$clone(deep = FALSE)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} } diff --git a/man/PM_cycle.Rd b/man/PM_cycle.Rd index d4fa2a402..4b228253c 100755 --- a/man/PM_cycle.Rd +++ b/man/PM_cycle.Rd @@ -34,9 +34,9 @@ allows you to access them in an S3 way, e.g. \code{run1$cycle$mean} if \code{run Michael Neely, Julian Otalvaro } \section{Public fields}{ - \if{html}{\out{
}} - \describe{ - \item{\code{data}}{A list with the following elements, which can also be extracted by name. +\if{html}{\out{
}} +\describe{ +\item{\code{data}}{A list with the following elements, which can also be extracted by name. e.e. \code{run1$cycle$objective}, which is equivalent to \code{run1$cycle$data$objective}. \strong{names} Vector of names of the random parameters \strong{objective} A tibble of -2*Log-likelihood, AIC and BIC at each cycle @@ -45,144 +45,140 @@ e.e. \code{run1$cycle$objective}, which is equivalent to \code{run1$cycle$data$o \strong{median} A tibble of cycle number and the median of each random parameter at each cycle, normalized to initial median \strong{sd} A tibble of cycle number and the standard deviation of each random parameter at each cycle, normalized to initial standard deviation \strong{status} Status of the last cycle: "Converged", "Maximum cycles reached", or "Posterior".} - } - \if{html}{\out{
}} +} +\if{html}{\out{
}} } \section{Active bindings}{ - \if{html}{\out{
}} - \describe{ - \item{\code{names}}{Vector of names of the random parameters} +\if{html}{\out{
}} +\describe{ +\item{\code{names}}{Vector of names of the random parameters} - \item{\code{objective}}{A tibble of -2*Log-likelihood, AIC and BIC at each cycle} +\item{\code{objective}}{A tibble of -2*Log-likelihood, AIC and BIC at each cycle} - \item{\code{gamlam}}{A tibble of cycle number and gamma or lambda at each cycle for each output equation} +\item{\code{gamlam}}{A tibble of cycle number and gamma or lambda at each cycle for each output equation} - \item{\code{mean}}{A tibble of cycle number and the mean of each random parameter +\item{\code{mean}}{A tibble of cycle number and the mean of each random parameter at each cycle, normalized to initial mean} - \item{\code{median}}{A tibble of cycle number and the median of each random +\item{\code{median}}{A tibble of cycle number and the median of each random parameter at each cycle, normalized to initial median} - \item{\code{sd}}{A tibble of cycle number and the standard deviation of each random parameter +\item{\code{sd}}{A tibble of cycle number and the standard deviation of each random parameter at each cycle, normalized to initial standard deviation} - \item{\code{status}}{Status of the last cycle: "Converged", "Maximum cycles reached", or "Posterior"} - } - \if{html}{\out{
}} +\item{\code{status}}{Status of the last cycle: "Converged", "Maximum cycles reached", or "Posterior"} +} +\if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ - \itemize{ - \item \href{#method-PM_cycle-initialize}{\code{PM_cycle$new()}} - \item \href{#method-PM_cycle-plot}{\code{PM_cycle$plot()}} - \item \href{#method-PM_cycle-summary}{\code{PM_cycle$summary()}} - \item \href{#method-PM_cycle-print}{\code{PM_cycle$print()}} - \item \href{#method-PM_cycle-clone}{\code{PM_cycle$clone()}} - } +\itemize{ +\item \href{#method-PM_cycle-new}{\code{PM_cycle$new()}} +\item \href{#method-PM_cycle-plot}{\code{PM_cycle$plot()}} +\item \href{#method-PM_cycle-summary}{\code{PM_cycle$summary()}} +\item \href{#method-PM_cycle-print}{\code{PM_cycle$print()}} +\item \href{#method-PM_cycle-clone}{\code{PM_cycle$clone()}} +} } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_cycle-initialize}{}}} -\subsection{\code{PM_cycle$new()}}{ - Create new object populated with cycle information - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_cycle$new(PMdata = NULL, path = ".", ...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{PMdata}}{include Saved, parsed output of prior run, used when source files are not available. +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_cycle-new}{}}} +\subsection{Method \code{new()}}{ +Create new object populated with cycle information +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_cycle$new(PMdata = NULL, path = ".", ...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{PMdata}}{include Saved, parsed output of prior run, used when source files are not available. .} - \item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. + +\item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. .} - \item{\code{...}}{Not currently used.} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - Creation of new \code{PM_cycle} object is automatic and not generally necessary + +\item{\code{...}}{Not currently used.} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +Creation of new \code{PM_cycle} object is automatic and not generally necessary for the user to do. - } } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_cycle-plot}{}}} -\subsection{\code{PM_cycle$plot()}}{ - Plot method - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_cycle$plot(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{plot.PM_cycle}} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - See \link{plot.PM_cycle}. - } +\subsection{Method \code{plot()}}{ +Plot method +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_cycle$plot(...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{plot.PM_cycle}} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +See \link{plot.PM_cycle}. } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_cycle-summary}{}}} -\subsection{\code{PM_cycle$summary()}}{ - Summary method - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_cycle$summary(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{summary.PM_cycle}} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - See \link{summary.PM_cycle}. - } +\subsection{Method \code{summary()}}{ +Summary method +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_cycle$summary(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{summary.PM_cycle}} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +See \link{summary.PM_cycle}. +} + +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_cycle-print}{}}} -\subsection{\code{PM_cycle$print()}}{ - Print method - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_cycle$print()} - \if{html}{\out{
}} - } - \subsection{Details}{ - Prints the last cycle summary information. - } +\subsection{Method \code{print()}}{ +Print method +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_cycle$print()}\if{html}{\out{
}} } +\subsection{Details}{ +Prints the last cycle summary information. +} + +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_cycle-clone}{}}} -\subsection{\code{PM_cycle$clone()}}{ - The objects of this class are cloneable with this method. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_cycle$clone(deep = FALSE)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{deep}}{Whether to make a deep clone.} - } - \if{html}{\out{
}} - } +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_cycle$clone(deep = FALSE)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} } diff --git a/man/PM_data.Rd b/man/PM_data.Rd index 7d21fc9bc..dc1f131b3 100755 --- a/man/PM_data.Rd +++ b/man/PM_data.Rd @@ -35,7 +35,7 @@ is particularly useful for making simulation templates on the fly. \examples{ ## ------------------------------------------------ -## Method `PM_data$addEvent()` +## Method `PM_data$addEvent` ## ------------------------------------------------ \dontrun{ @@ -44,53 +44,53 @@ out = NA, wt = 75)$addEvent(id = 1, time = 60, out = -1) } } \section{Public fields}{ - \if{html}{\out{
}} - \describe{ - \item{\code{data}}{Data frame containing the data to be modeled} +\if{html}{\out{
}} +\describe{ +\item{\code{data}}{Data frame containing the data to be modeled} - \item{\code{standard_data}}{Data frame containing standardized version of the data} +\item{\code{standard_data}}{Data frame containing standardized version of the data} - \item{\code{pop}}{The \verb{$data} field from a \link{PM_pop} object. This makes it easy to add population predictions to a raw data plot. This field will be \code{NULL} until the \link{PM_data} object is added to the \link{PM_result} after a run. As examples: +\item{\code{pop}}{The \verb{$data} field from a \link{PM_pop} object. This makes it easy to add population predictions to a raw data plot. This field will be \code{NULL} until the \link{PM_data} object is added to the \link{PM_result} after a run. As examples: \itemize{ \item \code{dat <- PM_data$new("data.csv")}. Here, \code{dat$pop} will be \code{NULL}. \item \code{run1 <- PM_load(1)}. Here, \code{run1$data$pop} will be the same as \code{run1$pop$data}. }} - \item{\code{post}}{The \verb{$data} field from a \link{PM_post} object. See details in the \code{pop} argument above.} - } - \if{html}{\out{
}} +\item{\code{post}}{The \verb{$data} field from a \link{PM_post} object. See details in the \code{pop} argument above.} +} +\if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ - \itemize{ - \item \href{#method-PM_data-initialize}{\code{PM_data$new()}} - \item \href{#method-PM_data-save}{\code{PM_data$save()}} - \item \href{#method-PM_data-auc}{\code{PM_data$auc()}} - \item \href{#method-PM_data-nca}{\code{PM_data$nca()}} - \item \href{#method-PM_data-plot}{\code{PM_data$plot()}} - \item \href{#method-PM_data-print}{\code{PM_data$print()}} - \item \href{#method-PM_data-summary}{\code{PM_data$summary()}} - \item \href{#method-PM_data-addEvent}{\code{PM_data$addEvent()}} - \item \href{#method-PM_data-clone}{\code{PM_data$clone()}} - } +\itemize{ +\item \href{#method-PM_data-new}{\code{PM_data$new()}} +\item \href{#method-PM_data-save}{\code{PM_data$save()}} +\item \href{#method-PM_data-auc}{\code{PM_data$auc()}} +\item \href{#method-PM_data-nca}{\code{PM_data$nca()}} +\item \href{#method-PM_data-plot}{\code{PM_data$plot()}} +\item \href{#method-PM_data-print}{\code{PM_data$print()}} +\item \href{#method-PM_data-summary}{\code{PM_data$summary()}} +\item \href{#method-PM_data-addEvent}{\code{PM_data$addEvent()}} +\item \href{#method-PM_data-clone}{\code{PM_data$clone()}} +} } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_data-initialize}{}}} -\subsection{\code{PM_data$new()}}{ - Create new data object - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_data$new(data = NULL, dt = NULL, quiet = FALSE, validate = TRUE, ...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{data}}{A quoted name of a file with full path if not +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_data-new}{}}} +\subsection{Method \code{new()}}{ +Create new data object +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_data$new(data = NULL, dt = NULL, quiet = FALSE, validate = TRUE, ...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{data}}{A quoted name of a file with full path if not in the working directory, an unquoted name of a data frame in the current R environment, or a \link{PM_data} object, which will rebuild it.} - \item{\code{dt}}{Pmetrics will try a variety of date/time formats. If all 16 of + +\item{\code{dt}}{Pmetrics will try a variety of date/time formats. If all 16 of them fail, use this parameter to specify the correct format as a character vector whose first element is date format and second is time. Use the following abbreviations: @@ -106,175 +106,175 @@ Always check to make sure that dates/times were parsed correctly and the relative times in the \code{PM_data$standard_data} field look correct. Other date/time formats are possible. See \code{\link[lubridate:parse_date_time]{lubridate::parse_date_time()}} for these. }} - \item{\code{quiet}}{Quietly validate. Default is \code{FALSE}.} - \item{\code{validate}}{Check for errors. Default is \code{TRUE}. Strongly recommended.} - \item{\code{...}}{Other arguments (not currently used).} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - Creation of a new \link{PM_data} objects from a file or + +\item{\code{quiet}}{Quietly validate. Default is \code{FALSE}.} + +\item{\code{validate}}{Check for errors. Default is \code{TRUE}. Strongly recommended.} + +\item{\code{...}}{Other arguments (not currently used).} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +Creation of a new \link{PM_data} objects from a file or a data frame. Data will be standardized and checked automatically to a fully specified, valid data object. - } } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_data-save}{}}} -\subsection{\code{PM_data$save()}}{ - Save data to file - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_data$save(file_name, ...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{file_name}}{A quoted name of the file to create +\subsection{Method \code{save()}}{ +Save data to file +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_data$save(file_name, ...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{file_name}}{A quoted name of the file to create with full path if not in the working directory.} - \item{\code{...}}{Arguments passed to \link{PMwriteMatrix}} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - Saves a delimited file (e.g. comma-separated) + +\item{\code{...}}{Arguments passed to \link{PMwriteMatrix}} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +Saves a delimited file (e.g. comma-separated) from the \code{standard_data} field - } } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_data-auc}{}}} -\subsection{\code{PM_data$auc()}}{ - Calculate AUC - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_data$auc(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{make_AUC}.} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - See \link{make_AUC}. - } +\subsection{Method \code{auc()}}{ +Calculate AUC +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_data$auc(...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{make_AUC}.} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +See \link{make_AUC}. } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_data-nca}{}}} -\subsection{\code{PM_data$nca()}}{ - Perform non-compartmental analysis - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_data$nca(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{make_NCA}.} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - See \link{make_NCA}. - } +\subsection{Method \code{nca()}}{ +Perform non-compartmental analysis +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_data$nca(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{make_NCA}.} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +See \link{make_NCA}. +} + +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_data-plot}{}}} -\subsection{\code{PM_data$plot()}}{ - Plot method - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_data$plot(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{plot.PM_data}} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - See \link{plot.PM_data}. - } +\subsection{Method \code{plot()}}{ +Plot method +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_data$plot(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{plot.PM_data}} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +See \link{plot.PM_data}. +} + +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_data-print}{}}} -\subsection{\code{PM_data$print()}}{ - Print method - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_data$print(standard = F, viewer = T, ...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{standard}}{Display the standardized data if \code{TRUE}. +\subsection{Method \code{print()}}{ +Print method +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_data$print(standard = F, viewer = T, ...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{standard}}{Display the standardized data if \code{TRUE}. Default is \code{FALSE}.} - \item{\code{viewer}}{Display the Viewer if \code{TRUE}. + +\item{\code{viewer}}{Display the Viewer if \code{TRUE}. Default is \code{TRUE}.} - \item{\code{...}}{Other arguments to \link{print.data.frame}. Only + +\item{\code{...}}{Other arguments to \link{print.data.frame}. Only passed if \code{viewer = FALSE}.} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - Displays the PM_data object in a variety of ways. - } +} +\if{html}{\out{
}} +} +\subsection{Details}{ +Displays the PM_data object in a variety of ways. } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_data-summary}{}}} -\subsection{\code{PM_data$summary()}}{ - Summary method - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_data$summary(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{summary.PM_data}.} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - See \link{summary.PM_data}. - } +\subsection{Method \code{summary()}}{ +Summary method +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_data$summary(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{summary.PM_data}.} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +See \link{summary.PM_data}. +} + +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_data-addEvent}{}}} -\subsection{\code{PM_data$addEvent()}}{ - Add events to PM_data object - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_data$addEvent(..., dt = NULL, quiet = FALSE, validate = FALSE)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Column names and values.} - \item{\code{dt}}{Pmetrics will try a variety of date/time formats. If all 16 of +\subsection{Method \code{addEvent()}}{ +Add events to PM_data object +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_data$addEvent(..., dt = NULL, quiet = FALSE, validate = FALSE)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Column names and values.} + +\item{\code{dt}}{Pmetrics will try a variety of date/time formats. If all 16 of them fail, use this parameter to specify the correct format as a character vector whose first element is date format and second is time. Use the following abbreviations: @@ -290,17 +290,19 @@ Always check to make sure that dates/times were parsed correctly and the relative times in the \code{PM_data$standard_data} field look correct. Other date/time formats are possible. See \code{\link[lubridate:parse_date_time]{lubridate::parse_date_time()}} for these. }} - \item{\code{quiet}}{Quietly validate. Default is \code{FALSE}.} - \item{\code{validate}}{Validate the new row or not. Default is \code{FALSE} as a new row + +\item{\code{quiet}}{Quietly validate. Default is \code{FALSE}.} + +\item{\code{validate}}{Validate the new row or not. Default is \code{FALSE} as a new row added to a blank will result in a one-row data object, which is invalid. Also, only one event type (dose or observation) should be added at a time, so if the new object contains only doses while building, this would cause an error. You should set \code{validate = TRUE} for the final addition.} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - Add lines to a PM_data object by supplying named columns and values. +} +\if{html}{\out{
}} +} +\subsection{Details}{ +Add lines to a PM_data object by supplying named columns and values. \code{ID} is always required. \code{Time} is handled differently depending on the sequence of \code{addEvent} calls (see \strong{Chaining} below). \itemize{ @@ -333,33 +335,35 @@ As we have seen in the examples above, \code{ADDL} and \code{II} are supported. to create a blank data object and then add rows. This can be particularly useful for creating simulation templates. See the example. - } - \subsection{Examples}{ - \if{html}{\out{
}} - \preformatted{PM_data$new()$addEvent(id = 1, time = 0, dose = 100, addl = 4, ii = 12, +} + +\subsection{Examples}{ +\if{html}{\out{
}} +\preformatted{\dontrun{ +PM_data$new()$addEvent(id = 1, time = 0, dose = 100, addl = 4, ii = 12, out = NA, wt = 75)$addEvent(id = 1, time = 60, out = -1) } - \if{html}{\out{
}} - } +} +\if{html}{\out{
}} + } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_data-clone}{}}} -\subsection{\code{PM_data$clone()}}{ - The objects of this class are cloneable with this method. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_data$clone(deep = FALSE)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{deep}}{Whether to make a deep clone.} - } - \if{html}{\out{
}} - } +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_data$clone(deep = FALSE)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} } diff --git a/man/PM_final.Rd b/man/PM_final.Rd index b3812e005..3500b1e0c 100755 --- a/man/PM_final.Rd +++ b/man/PM_final.Rd @@ -34,9 +34,9 @@ allows you to access them in an S3 way, e.g. \code{run1$final$popMean} if \code{ Michael Neely, Julian Otalvaro } \section{Public fields}{ - \if{html}{\out{
}} - \describe{ - \item{\code{data}}{A list with the following elements, which can also be extracted by name. +\if{html}{\out{
}} +\describe{ +\item{\code{data}}{A list with the following elements, which can also be extracted by name. \itemize{ \item \strong{popPoints} (NPAG only) Data frame of the final cycle joint population density of grid points with column names equal to the name of each random parameter plus \emph{prob} for the @@ -70,55 +70,55 @@ the medians of the posterior distributions for each parameter. \item \strong{nsub} Number of subjects \item \strong{ab} Tibble/data frame of boundaries for random parameter values with columns: name, lower, upper. }} - } - \if{html}{\out{
}} +} +\if{html}{\out{
}} } \section{Active bindings}{ - \if{html}{\out{
}} - \describe{ - \item{\code{popPoints}}{(NPAG only) Data frame of the final cycle joint population density of grid points +\if{html}{\out{
}} +\describe{ +\item{\code{popPoints}}{(NPAG only) Data frame of the final cycle joint population density of grid points with column names equal to the name of each random parameter plus \emph{prob} for the associated probability of that point} - \item{\code{popMean}}{The final cycle mean for each random parameter distribution} +\item{\code{popMean}}{The final cycle mean for each random parameter distribution} - \item{\code{popSD}}{The final cycle standard deviation for each random parameter distribution} +\item{\code{popSD}}{The final cycle standard deviation for each random parameter distribution} - \item{\code{popCV}}{The final cycle coefficient of variation (SD/Mean) for each random parameter distribution} +\item{\code{popCV}}{The final cycle coefficient of variation (SD/Mean) for each random parameter distribution} - \item{\code{popVar}}{The final cycle variance for each random parameter distribution} +\item{\code{popVar}}{The final cycle variance for each random parameter distribution} - \item{\code{popCov}}{The final cycle random parameter covariance matrix} +\item{\code{popCov}}{The final cycle random parameter covariance matrix} - \item{\code{popCor}}{The final cycle random parameter correlation matrix} +\item{\code{popCor}}{The final cycle random parameter correlation matrix} - \item{\code{popMed}}{The final cycle median values for each random parameter, +\item{\code{popMed}}{The final cycle median values for each random parameter, i.e. those that have unknown mean and unknown variance, both of which are fitted during the run} - \item{\code{postPoints}}{(NPAG only) Data frame of posterior population points for each of the first 100 subject, +\item{\code{postPoints}}{(NPAG only) Data frame of posterior population points for each of the first 100 subject, with columns id, point, parameters and probability. The first column is the subject, the second column has the population point number, followed by the values for the parameters in that point and the probability.} - \item{\code{postMean}}{A \emph{nsub} x \emph{npar} data frame containing +\item{\code{postMean}}{A \emph{nsub} x \emph{npar} data frame containing the means of the posterior distributions for each parameter.} - \item{\code{postSD}}{A \emph{nsub} x \emph{npar} data frame containing +\item{\code{postSD}}{A \emph{nsub} x \emph{npar} data frame containing the SDs of the posterior distributions for each parameter.} - \item{\code{postVar}}{A \emph{nsub} x \emph{npar} data frame containing +\item{\code{postVar}}{A \emph{nsub} x \emph{npar} data frame containing the variances of the posterior distributions for each parameter.} - \item{\code{postCov}}{NPAG only: An list of length \emph{nsub}, each element with an \emph{npar} x \emph{npar} data frame +\item{\code{postCov}}{NPAG only: An list of length \emph{nsub}, each element with an \emph{npar} x \emph{npar} data frame that contains the posterior parameter value covariances for that subject.} - \item{\code{postCor}}{NPAG only: An list of length \emph{nsub}, each element with an \emph{npar} x \emph{npar} data frame +\item{\code{postCor}}{NPAG only: An list of length \emph{nsub}, each element with an \emph{npar} x \emph{npar} data frame that contains the posterior parameter value correlations for that subject.} - \item{\code{postMed}}{A \emph{nsub} x \emph{npar} data frame containing +\item{\code{postMed}}{A \emph{nsub} x \emph{npar} data frame containing the medians of the posterior distributions for each parameter.*} - \item{\code{shrinkage}}{A data frame with the shrinkage for each parameter. +\item{\code{shrinkage}}{A data frame with the shrinkage for each parameter. The total population variance for a parameter is comprised of variance(EBE) plus average variance(EBD), where each subject's EBE is the Empirical Bayes Estimate or mean posterior value @@ -156,111 +156,108 @@ In contrast, badly undersampled subjects can result in only one support point. There is no formal criterion for this statistic, but it can be used in combination with shrinkage to assess the information content of the data.} - \item{\code{gridpts}}{(NPAG only) Initial number of support points} +\item{\code{gridpts}}{(NPAG only) Initial number of support points} - \item{\code{nsub}}{Number of subjects} +\item{\code{nsub}}{Number of subjects} - \item{\code{ab}}{Matrix of boundaries for random parameter values} - } - \if{html}{\out{
}} +\item{\code{ab}}{Matrix of boundaries for random parameter values} +} +\if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ - \itemize{ - \item \href{#method-PM_final-initialize}{\code{PM_final$new()}} - \item \href{#method-PM_final-plot}{\code{PM_final$plot()}} - \item \href{#method-PM_final-summary}{\code{PM_final$summary()}} - \item \href{#method-PM_final-clone}{\code{PM_final$clone()}} - } +\itemize{ +\item \href{#method-PM_final-new}{\code{PM_final$new()}} +\item \href{#method-PM_final-plot}{\code{PM_final$plot()}} +\item \href{#method-PM_final-summary}{\code{PM_final$summary()}} +\item \href{#method-PM_final-clone}{\code{PM_final$clone()}} +} } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_final-initialize}{}}} -\subsection{\code{PM_final$new()}}{ - Create new object populated with final cycle information - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_final$new(PMdata = NULL, path = ".", ...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{PMdata}}{include Saved, parsed output of prior run, used when source files are not available. +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_final-new}{}}} +\subsection{Method \code{new()}}{ +Create new object populated with final cycle information +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_final$new(PMdata = NULL, path = ".", ...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{PMdata}}{include Saved, parsed output of prior run, used when source files are not available. .} - \item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. + +\item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. .} - \item{\code{...}}{Not currently used.} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - Creation of new \code{PM_final} object is automatic and not generally necessary + +\item{\code{...}}{Not currently used.} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +Creation of new \code{PM_final} object is automatic and not generally necessary for the user to do. - } } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_final-plot}{}}} -\subsection{\code{PM_final$plot()}}{ - Plot method - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_final$plot(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{plot.PM_final}} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - See \link{plot.PM_final}. - } +\subsection{Method \code{plot()}}{ +Plot method +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_final$plot(...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{plot.PM_final}} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +See \link{plot.PM_final}. } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_final-summary}{}}} -\subsection{\code{PM_final$summary()}}{ - Summary method - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_final$summary(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{summary.PM_final}} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - See \link{summary.PM_final}. - } +\subsection{Method \code{summary()}}{ +Summary method +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_final$summary(...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{summary.PM_final}} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +See \link{summary.PM_final}. } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_final-clone}{}}} -\subsection{\code{PM_final$clone()}}{ - The objects of this class are cloneable with this method. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_final$clone(deep = FALSE)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{deep}}{Whether to make a deep clone.} - } - \if{html}{\out{
}} - } +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_final$clone(deep = FALSE)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} } diff --git a/man/PM_fit.Rd b/man/PM_fit.Rd index 4d11b4348..bc5612aec 100755 --- a/man/PM_fit.Rd +++ b/man/PM_fit.Rd @@ -15,143 +15,133 @@ methods to cross-check data and model objects for compatibility. } \keyword{internal} \section{Public fields}{ - \if{html}{\out{
}} - \describe{ - \item{\code{data}}{\link{PM_data} object} +\if{html}{\out{
}} +\describe{ +\item{\code{data}}{\link{PM_data} object} - \item{\code{model}}{\link{PM_model} object} - } - \if{html}{\out{
}} +\item{\code{model}}{\link{PM_model} object} +} +\if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ - \itemize{ - \item \href{#method-PM_fit-initialize}{\code{PM_fit$new()}} - \item \href{#method-PM_fit-run}{\code{PM_fit$run()}} - \item \href{#method-PM_fit-save}{\code{PM_fit$save()}} - \item \href{#method-PM_fit-load}{\code{PM_fit$load()}} - \item \href{#method-PM_fit-check}{\code{PM_fit$check()}} - \item \href{#method-PM_fit-clone}{\code{PM_fit$clone()}} - } +\itemize{ +\item \href{#method-PM_fit-new}{\code{PM_fit$new()}} +\item \href{#method-PM_fit-run}{\code{PM_fit$run()}} +\item \href{#method-PM_fit-save}{\code{PM_fit$save()}} +\item \href{#method-PM_fit-load}{\code{PM_fit$load()}} +\item \href{#method-PM_fit-check}{\code{PM_fit$check()}} +\item \href{#method-PM_fit-clone}{\code{PM_fit$clone()}} +} } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_fit-initialize}{}}} -\subsection{\code{PM_fit$new()}}{ - Create a new object - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_fit$new(data = data, model = model)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{data}}{Either the name of a \link{PM_data} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_fit-new}{}}} +\subsection{Method \code{new()}}{ +Create a new object +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_fit$new(data = data, model = model)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{data}}{Either the name of a \link{PM_data} object in memory or the quoted name of a Pmetrics data file in the current working directory, which will crate a \link{PM_data} object on the fly. However, if created on the fly, this object will not be available to other methods or other instances of \link{PM_fit}.} - \item{\code{model}}{Similarly, this is either the name of a \link{PM_model} + +\item{\code{model}}{Similarly, this is either the name of a \link{PM_model} object in memory or the quoted name of a Pmetrics text model file in the current working directory. Again, if created on the fly, the object will not be available to other methods or other instances of \link{PM_fit}.} - } - \if{html}{\out{
}} - } } - +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_fit-run}{}}} -\subsection{\code{PM_fit$run()}}{ - Run a fit of model to the data (deprecated) - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_fit$run()} - \if{html}{\out{
}} - } - \subsection{Details}{ - The \verb{$run} method for \code{PM_fit} objects has been deprecated in +\subsection{Method \code{run()}}{ +Run a fit of model to the data (deprecated) +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_fit$run()}\if{html}{\out{
}} +} + +\subsection{Details}{ +The \verb{$run} method for \code{PM_fit} objects has been deprecated in favor of the \verb{$fit} method in \code{\link[=PM_model]{PM_model()}}. - } } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_fit-save}{}}} -\subsection{\code{PM_fit$save()}}{ - Save the current PM_fit object to a .rds file. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_fit$save(file_name = "PMfit.rds")} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{file_name}}{Name of the file to be created. The -default is "PMfit.rds".} - } - \if{html}{\out{
}} - } +\subsection{Method \code{save()}}{ +Save the current PM_fit object to a .rds file. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_fit$save(file_name = "PMfit.rds")}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{file_name}}{Name of the file to be created. The +default is "PMfit.rds".} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_fit-load}{}}} -\subsection{\code{PM_fit$load()}}{ - \code{PM_fit} objects contain a \code{save} method which invokes \link{saveRDS} to write +\subsection{Method \code{load()}}{ +\code{PM_fit} objects contain a \code{save} method which invokes \link{saveRDS} to write the object to the hard drive as an .rds file. This is the corresponding load function. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_fit$load(file_name)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{file_name}}{Name of the file to be read, the default is "PMfit.rds".} - } - \if{html}{\out{
}} - } - \subsection{Returns}{ - A \code{PM_fit} object. - } +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_fit$load(file_name)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{file_name}}{Name of the file to be read, the default is "PMfit.rds".} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +A \code{PM_fit} object. +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_fit-check}{}}} -\subsection{\code{PM_fit$check()}}{ - Checks for errors in data and model objects and agreement between them. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_fit$check()} - \if{html}{\out{
}} - } +\subsection{Method \code{check()}}{ +Checks for errors in data and model objects and agreement between them. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_fit$check()}\if{html}{\out{
}} } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_fit-clone}{}}} -\subsection{\code{PM_fit$clone()}}{ - The objects of this class are cloneable with this method. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_fit$clone(deep = FALSE)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{deep}}{Whether to make a deep clone.} - } - \if{html}{\out{
}} - } +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_fit$clone(deep = FALSE)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} } diff --git a/man/PM_model.Rd b/man/PM_model.Rd index f9e593fa2..30ecbe845 100755 --- a/man/PM_model.Rd +++ b/man/PM_model.Rd @@ -74,37 +74,37 @@ mod <- PM_model$new(mod_list) Michael Neely } \section{Public fields}{ - \if{html}{\out{
}} - \describe{ - \item{\code{model_list}}{A list containing the model components built by translating -the original arguments into Rust} +\if{html}{\out{
}} +\describe{ +\item{\code{model_list}}{A list containing normalized model metadata derived from +the original R model blocks} - \item{\code{arg_list}}{A list containing the original arguments passed to the model} - - \item{\code{binary_path}}{The full path and filename of the compiled model} - } - \if{html}{\out{
}} +\item{\code{arg_list}}{A list containing the original arguments passed to the model} +} +\if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ - \itemize{ - \item \href{#method-PM_model-initialize}{\code{PM_model$new()}} - \item \href{#method-PM_model-print}{\code{PM_model$print()}} - \item \href{#method-PM_model-plot}{\code{PM_model$plot()}} - \item \href{#method-PM_model-fit}{\code{PM_model$fit()}} - \item \href{#method-PM_model-map}{\code{PM_model$map()}} - \item \href{#method-PM_model-sim}{\code{PM_model$sim()}} - \item \href{#method-PM_model-compile}{\code{PM_model$compile()}} - \item \href{#method-PM_model-save}{\code{PM_model$save()}} - \item \href{#method-PM_model-copy}{\code{PM_model$copy()}} - \item \href{#method-PM_model-clone}{\code{PM_model$clone()}} - } +\itemize{ +\item \href{#method-PM_model-new}{\code{PM_model$new()}} +\item \href{#method-PM_model-print}{\code{PM_model$print()}} +\item \href{#method-PM_model-plot}{\code{PM_model$plot()}} +\item \href{#method-PM_model-fit}{\code{PM_model$fit()}} +\item \href{#method-PM_model-map}{\code{PM_model$map()}} +\item \href{#method-PM_model-sim}{\code{PM_model$sim()}} +\item \href{#method-PM_model-dsl}{\code{PM_model$dsl()}} +\item \href{#method-PM_model-debug_dsl}{\code{PM_model$debug_dsl()}} +\item \href{#method-PM_model-compile}{\code{PM_model$compile()}} +\item \href{#method-PM_model-save}{\code{PM_model$save()}} +\item \href{#method-PM_model-copy}{\code{PM_model$copy()}} +\item \href{#method-PM_model-clone}{\code{PM_model$clone()}} +} } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_model-initialize}{}}} -\subsection{\code{PM_model$new()}}{ - This is the method to create a new \code{PM_model} object. +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_model-new}{}}} +\subsection{Method \code{new()}}{ +This is the method to create a new \code{PM_model} object. The first argument allows creation of a model from a variety of pre-existing sources, and if used, all the subsequent arguments will be ignored. If a model @@ -143,9 +143,8 @@ Again, all R code will be converted to lowercase prior to translation into Rust. } \strong{Important:} All models must have \code{pri}, \code{eqn}, \code{out}, and \code{err} blocks. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_model$new( +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_model$new( x = NULL, pri = NULL, cov = NULL, @@ -158,13 +157,13 @@ Again, all R code will be converted to lowercase prior to translation into Rust. err = NULL, solver = NULL, ... -)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{x}}{An optional argument, but if specified, all the subsequent +)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{x}}{An optional argument, but if specified, all the subsequent arguments will be ignored. \code{x} creates a \code{PM_model} from existing appropriate input, which can be one of the following: \itemize{ @@ -187,7 +186,8 @@ prior example: \code{PM_model$new(mod)} } See the user manual \code{\link[=PM_manual]{PM_manual()}} for more help on directly defining models in R.} - \item{\code{pri}}{The first of the arguments used if \code{x} is not specified. This is + +\item{\code{pri}}{The first of the arguments used if \code{x} is not specified. This is a named list of primary parameters, which are the model parameters that are estimated in the population analysis. They are specified by one of two creator functions: \code{\link[=ab]{ab()}} or \code{\link[=msd]{msd()}}. For example, @@ -201,7 +201,8 @@ by one of two creator functions: \code{\link[=ab]{ab()}} or \code{\link[=msd]{ms The \code{\link[=ab]{ab()}} creator specifies the initial range \verb{[a, b]} of the parameter, while the \code{\link[=msd]{msd()}} creator specifies the initial mean and standard deviation of the parameter.} - \item{\code{cov}}{A list whose names are some or all of the covariates in the data file. + +\item{\code{cov}}{A list whose names are some or all of the covariates in the data file. Unlike prior versions of Pmetrics, as of 3.0.0, they do not have to be listed in the same order as in the data file, and they do not need to be all present. \strong{Only those covariates you wish to use in model equations or analyze for relationships to model parameters need to be declared here.} @@ -221,7 +222,8 @@ For example: }\if{html}{\out{
}} Note that \code{wt = interp()} is equivalent to \code{wt = interp("lm")}, since "lm" is the default.} - \item{\code{sec}}{A function defining the secondary (global) equations in the model. Values + +\item{\code{sec}}{A function defining the secondary (global) equations in the model. Values are not estimated for these equations but they are available to every other block in the model. For example: @@ -234,7 +236,8 @@ Note that the function must be defined with no arguments between the parentheses, and the body \strong{must be in R syntax}. Any number of lines and R code, e.g. \code{if} - \verb{else} statements, etc. are permissible.} - \item{\code{eqn}}{A function defining the model equations. The function must have no arguments. + +\item{\code{eqn}}{A function defining the model equations. The function must have no arguments. The body of the function may contain three kinds of equations, written in R syntax. \itemize{ \item \strong{Implicit equations} referenced by calling the name of a Pmetrics model library object @@ -291,7 +294,8 @@ For example, the \code{sec} block, but will only be available within the \code{eqn} block as opposed to global availability when defined in \code{sec}. They can be added to either }} - \item{\code{lag}}{A function defining the lag time (delayed absorption) for the bolus input. + +\item{\code{lag}}{A function defining the lag time (delayed absorption) for the bolus input. The function must have no arguments, and the equations must be defined in R syntax The equations must be defined in the form of @@ -309,7 +313,8 @@ this code could be used to model delayed absorption if an antacid is present. As for \code{eqn}, additional equations in R code can be defined in this block, but will only be available within the \code{lag} block.} - \item{\code{fa}}{A function defining the bioavailability (fraction absorbed) equations, + +\item{\code{fa}}{A function defining the bioavailability (fraction absorbed) equations, similar to \code{lag}. Example: @@ -321,7 +326,8 @@ Example: As for \code{eqn}, additional equations in R code can be defined in this block, but will only be available within the \code{fa} block.} - \item{\code{ini}}{A function defining the initial conditions for a compartment + +\item{\code{ini}}{A function defining the initial conditions for a compartment in the model. Structure is similar to \code{lag} and \code{fa}. Example: @@ -338,7 +344,8 @@ of a covariate \code{init2} multiplied by the volume of the compartment, As for \code{eqn}, additional equations in R code can be defined in this block, but will only be available within the \code{ini} block.} - \item{\code{out}}{A function defining the output equations, which are the predictions + +\item{\code{out}}{A function defining the output equations, which are the predictions from the model. The function must have no arguments, and the equations for predictions must be defined in R syntax. @@ -398,7 +405,8 @@ equation since there is only one source of observations: plasma concentration. AUC (\code{y[2]}) is not fitted to any observations; it is a calculation based on the model state, given the optimized parameter values. It's not required, but shown here for illustrative purposes.} - \item{\code{err}}{An unammed vector of error models for each of the output equations + +\item{\code{err}}{An unammed vector of error models for each of the output equations with observations, i.e. those that have an \code{outeq} number associated with them in the data. Each error model is defined by the \code{\link[=proportional]{proportional()}} creator or @@ -427,68 +435,66 @@ for 3 outputs with the same error model: replicate(3, proportional(2, c(0.1, 0.15, 0, 0))) ) }\if{html}{\out{
}}} - \item{\code{solver}}{Optional ODE solver for user-defined ODE models. + +\item{\code{solver}}{Optional ODE solver for user-defined ODE models. Supported values are "BDF", "TRBDF2", "ESDIRK34", and "TSIT45". This is ignored for analytical library models.} - \item{\code{...}}{Not currently used.} - } - \if{html}{\out{}} - } -} +\item{\code{...}}{Not currently used.} +} +\if{html}{\out{}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_model-print}{}}} -\subsection{\code{PM_model$print()}}{ - Print the model summary. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_model$print(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Not used.} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - This method prints a summary of the model. - } +\subsection{Method \code{print()}}{ +Print the model summary. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_model$print(...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Not used.} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +This method prints a summary of the model. } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_model-plot}{}}} -\subsection{\code{PM_model$plot()}}{ - Plot the model. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_model$plot(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Additional arguments passed to the plot function.} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - This method plots the model using the +\subsection{Method \code{plot()}}{ +Plot the model. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_model$plot(...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Additional arguments passed to the plot function.} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +This method plots the model using the \code{\link[=plot.PM_model]{plot.PM_model()}} function. - } } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_model-fit}{}}} -\subsection{\code{PM_model$fit()}}{ - This is the main method to run a population analysis. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_model$fit( +\subsection{Method \code{fit()}}{ +This is the main method to run a population analysis. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_model$fit( data = NULL, path = ".", run = NULL, @@ -497,6 +503,11 @@ This is ignored for analytical library models.} cycles = 100, prior = "sobol", points = 100, + cache = TRUE, + progress = TRUE, + write_logs = FALSE, + stdout_logs = TRUE, + log_level = "INFO", idelta = 0.1, tad = 0, seed = 23, @@ -504,13 +515,13 @@ This is ignored for analytical library models.} algorithm = "NPAG", report = getPMoptions("report_template"), quiet = FALSE -)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{data}}{Either the name of a \link{PM_data} +)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{data}}{Either the name of a \link{PM_data} object in memory or the quoted filename (with or without a path) of a Pmetrics data file. If the path is not specified, the file is assumed to be in the current working directory, unless the \code{path} argument below is also specified as a global option for the fit. @@ -518,16 +529,22 @@ The file will be used to create a \link{PM_data} object on the fly. However, if created on the fly, this object will not be available to other methods or other instances of \verb{$fit()}.} - \item{\code{path}}{Optional full path or relative path from current working directory + +\item{\code{path}}{Optional full path or relative path from current working directory to the folder where \code{data} and \code{model} are located if specified as filenames without their own paths, and where the output will be saved. Default is the current working directory.} - \item{\code{run}}{Specify the run number of the output folder. Default if missing is the next available number.} - \item{\code{include}}{Vector of subject id values in the data file to include in the analysis. + +\item{\code{run}}{Specify the run number of the output folder. Default if missing is the next available number.} + +\item{\code{include}}{Vector of subject id values in the data file to include in the analysis. The default (missing) is all.} - \item{\code{exclude}}{A vector of subject IDs to exclude in the analysis, e.g. \code{c(4,6:14,16:20)}} - \item{\code{cycles}}{Number of cycles to run. Default is 100.} - \item{\code{prior}}{The distribution for the initial support points, which can be + +\item{\code{exclude}}{A vector of subject IDs to exclude in the analysis, e.g. \code{c(4,6:14,16:20)}} + +\item{\code{cycles}}{Number of cycles to run. Default is 100.} + +\item{\code{prior}}{The distribution for the initial support points, which can be one of several options. \itemize{ \item The default is "sobol", which is a semi-random distribution. This is the distribution @@ -556,7 +573,8 @@ as for the filename option above. Example: \code{fit1$run(prior = 2)}. is in the required format described in the filename option above. Example: \verb{mytheta <- read_csv("mytheta.csv"); fit1$run(prior = mytheta)}. }} - \item{\code{points}}{The number of initial support points if one of + +\item{\code{points}}{The number of initial support points if one of the semi-random, uniform distributions are selected in the \code{prior} argument above. Default is 100. The initial points are spread through the hyperspace defined by the random parameter ranges @@ -567,28 +585,52 @@ Pmetrics will suggest the minimum number of points that should be tried. The greater the initial number of points, the less chance of missing the globally maximally likely parameter value distribution, but the slower the run.} - \item{\code{idelta}}{How often to generate posterior predictions in units of time. + +\item{\code{cache}}{Logical. Whether to enable runtime prediction caching during fitting. +Default is \code{TRUE}.} + +\item{\code{progress}}{Logical. Whether to print runtime likelihood-matrix progress during +fitting. Default is \code{TRUE}.} + +\item{\code{write_logs}}{Logical. Whether to write fit tracing logs to +\code{outputs/log.txt}. Default is \code{FALSE}.} + +\item{\code{stdout_logs}}{Logical. Whether to print fit tracing logs in the R console. +Default is \code{TRUE}.} + +\item{\code{log_level}}{Character scalar. Fit tracing level. One of \code{"ERROR"}, +\code{"WARN"}, \code{"INFO"}, \code{"DEBUG"}, or \code{"TRACE"}. Default is +\code{"INFO"}.} + +\item{\code{idelta}}{How often to generate posterior predictions in units of time. Default is 0.1, which means a prediction is generated every 0.1 hours (6 minutes) if the unit of time is hours. Predictions are made at this interval until the time of the last event (dose or observation) or until \code{tad} if that value is greater than the time of the last dose or observation in the data.} - \item{\code{tad}}{Length of time after the last dose event to add additional predictions + +\item{\code{tad}}{Length of time after the last dose event to add additional predictions at frequency \code{idelta}. Default is 0, which means no additional predictions beyond the last dose, assuming the dose is the last event. . If the last observation in the data is after \code{tad}, then a prediction will be generated at time = \code{tad} after the last dose} - \item{\code{seed}}{Seed used if \code{prior = "sobol"}. Ignored otherwise.} - \item{\code{overwrite}}{Boolean operator to overwrite existing run result folders. Default is \code{FALSE}.} - \item{\code{algorithm}}{The algorithm to use for the run. Default is "NPAG" for the \strong{N}on-\strong{P}arametric \strong{A}daptive \strong{G}rid. Alternatives: "NPOD".} - \item{\code{report}}{If missing, the default Pmetrics report template as specified in \link{getPMoptions} + +\item{\code{seed}}{Seed used if \code{prior = "sobol"}. Ignored otherwise.} + +\item{\code{overwrite}}{Boolean operator to overwrite existing run result folders. Default is \code{FALSE}.} + +\item{\code{algorithm}}{The algorithm to use for the run. Default is "NPAG" for the \strong{N}on-\strong{P}arametric \strong{A}daptive \strong{G}rid. Alternatives: "NPOD".} + +\item{\code{report}}{If missing, the default Pmetrics report template as specified in \link{getPMoptions} is used. Otherwise can be "plotly", "ggplot", or "none".} - \item{\code{quiet}}{Boolean operator to suppress messages during the run. Default is \code{FALSE}.} - \item{\code{intern}}{Run NPAG in the R console without a batch script. Default is TRUE.} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - As of Pmetrics 3.0.0, models contain compiled code to fit + +\item{\code{quiet}}{Boolean operator to suppress messages during the run. Default is \code{FALSE}.} + +\item{\code{intern}}{Run NPAG in the R console without a batch script. Default is TRUE.} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +As of Pmetrics 3.0.0, models contain compiled code to fit the model equations to the data, optimizing the parameter value probability distributions in the population to maximize their likelihood, or more precisely, minimize @@ -605,18 +647,18 @@ and the algorithm to use, among others. The \verb{$fit()} method is the descendant of the legacy \link{NPrun} function, which is maintained as a wrapper to \verb{$fit()} for backwards compatibility. - } - \subsection{Returns}{ - A successful run will result in creation of a new folder in the working -directory with the results inside the folder. - } } +\subsection{Returns}{ +A successful run will result in creation of a new folder in the working +directory with the results inside the folder. +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_model-map}{}}} -\subsection{\code{PM_model$map()}}{ - Calculate posteriors from a fitted model. +\subsection{Method \code{map()}}{ +Calculate posteriors from a fitted model. #' @details This method calculates posteriors from a compiled model. It is not necessary to have run the model first, but it is necessary to have an informative prior distribution. @@ -629,123 +671,156 @@ To calculate the posteriors, \code{map()} calls the \code{fit()} method with the and the \code{algorithm} argument set to "POSTPROB". If \code{data} are not provided as an argument to \code{map()}, the model's \code{data} field is used instead. If \code{data} is provided, it must be a \link{PM_data} object or the pathname of a file which can be loaded as a \link{PM_data} object. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_model$map(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to the \code{fit} method. Note that the \code{cycles} argument is set to 0, -and the \code{algorithm} argument is set to "POSTPROB" automatically.} - } - \if{html}{\out{
}} - } +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_model$map(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to the \code{fit} method. Note that the \code{cycles} argument is set to 0, +and the \code{algorithm} argument is set to "POSTPROB" automatically.} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_model-sim}{}}} -\subsection{\code{PM_model$sim()}}{ - Simulate data from the model using a set of parameter values. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_model$sim(data, theta, quiet = FALSE)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{data}}{A \link{PM_data} object containing the dosing and observation information.} - \item{\code{theta}}{A matrix of parameter values to use for the simulation. +\subsection{Method \code{sim()}}{ +Simulate data from the model using a set of parameter values. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_model$sim(data, theta, quiet = FALSE)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{data}}{A \link{PM_data} object containing the dosing and observation information.} + +\item{\code{theta}}{A matrix of parameter values to use for the simulation. The \code{theta} matrix should have the same number of columns as the number of primary parameters in the model. Each row of \code{theta} represents a different set of parameter values.} - \item{\code{quiet}}{Logical, if TRUE, suppresses messages during simulation.} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - This method simulates output from the model using a set of parameter values + +\item{\code{quiet}}{Logical, if TRUE, suppresses messages during simulation.} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +This method simulates output from the model using a set of parameter values provided in the \code{theta} matrix and the template for dosing/observations in the \code{data} object. - } } +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_model-dsl}{}}} +\subsection{Method \code{dsl()}}{ +Return the lowered model DSL. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_model$dsl()}\if{html}{\out{
}} +} + +\subsection{Details}{ +This method returns the pharmsol DSL source generated from the current model. +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_model-debug_dsl}{}}} +\subsection{Method \code{debug_dsl()}}{ +Inspect the lowered model DSL and runtime validation result. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_model$debug_dsl(quiet = FALSE)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{quiet}}{Logical, if TRUE, suppresses messages and DSL printing.} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +This method is useful when you want to debug the exact DSL source that +Pmetrics sends to the runtime. It returns the lowered DSL, a line-numbered +copy, and either the rendered validation diagnostic or a success status. +} + +\subsection{Returns}{ +A list with \code{ok}, \code{stage}, \code{dsl}, \code{numbered_dsl}, and \code{diagnostic}. +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_model-compile}{}}} -\subsection{\code{PM_model$compile()}}{ - Compile the model to a binary file. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_model$compile(quiet = FALSE)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{quiet}}{Logical, if TRUE, suppresses messages during compilation.} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - This method write the model to a Rust file in a temporary path, -updates the \code{binary_path} field for the model, and compiles that -file to a binary file that can be used for fitting or simulation. - } +\subsection{Method \code{compile()}}{ +Validate the model DSL. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_model$compile(quiet = FALSE)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{quiet}}{Logical, if TRUE, suppresses messages during compilation.} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +This method lowers the model to pharmsol DSL and validates that source +with the runtime compiler used for fitting and simulation. } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_model-save}{}}} -\subsection{\code{PM_model$save()}}{ - Save model to file (deprecated). - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_model$save()} - \if{html}{\out{
}} - } - \subsection{Details}{ - This method is deprecated. Existing or manually created model files may be read with \code{PM_model$new(filename)}, +\subsection{Method \code{save()}}{ +Save model to file (deprecated). +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_model$save()}\if{html}{\out{
}} +} + +\subsection{Details}{ +This method is deprecated. Existing or manually created model files may be read with \code{PM_model$new(filename)}, but including model code in scripts is preferred, as this makes models used in runs transparent and more easily edited. Use the \code{PM_model$copy()} method instead to copy the model code to the clipboard and paste into scripts. - } } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_model-copy}{}}} -\subsection{\code{PM_model$copy()}}{ - Copy model code to clipboard. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_model$copy()} - \if{html}{\out{
}} - } - \subsection{Details}{ - This method copies the R code to create the model to the clipboard. +\subsection{Method \code{copy()}}{ +Copy model code to clipboard. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_model$copy()}\if{html}{\out{
}} +} + +\subsection{Details}{ +This method copies the R code to create the model to the clipboard. This is useful for saving the model code in a script. - } } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_model-clone}{}}} -\subsection{\code{PM_model$clone()}}{ - The objects of this class are cloneable with this method. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_model$clone(deep = FALSE)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{deep}}{Whether to make a deep clone.} - } - \if{html}{\out{
}} - } +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_model$clone(deep = FALSE)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} } diff --git a/man/PM_op.Rd b/man/PM_op.Rd index 28554c0f9..9e8ab0372 100755 --- a/man/PM_op.Rd +++ b/man/PM_op.Rd @@ -33,162 +33,159 @@ allows you to access them in an S3 way, e.g. \code{run1$op$time} if \code{run1} Michael Neely, Julian Otalvaro } \section{Public fields}{ - \if{html}{\out{
}} - \describe{ - \item{\code{id}}{subject identification} +\if{html}{\out{
}} +\describe{ +\item{\code{id}}{subject identification} - \item{\code{time}}{observation time in relative units, usually hours} +\item{\code{time}}{observation time in relative units, usually hours} - \item{\code{obs}}{observation} +\item{\code{obs}}{observation} - \item{\code{cens}}{censoring information: "none" for observed, "bloq" for below limit of quantification, "aloq" for above limit of quantification} +\item{\code{cens}}{censoring information: "none" for observed, "bloq" for below limit of quantification, "aloq" for above limit of quantification} - \item{\code{pred}}{prediction} +\item{\code{pred}}{prediction} - \item{\code{pred.type}}{Population predictions based on Bayesian prior parameter value distribution, +\item{\code{pred.type}}{Population predictions based on Bayesian prior parameter value distribution, or individual predictions based on Bayesian posterior parameter value distributions} - \item{\code{icen}}{Predictions based on mean or median of Bayesian \code{pred.type}parameter values} +\item{\code{icen}}{Predictions based on mean or median of Bayesian \code{pred.type}parameter values} - \item{\code{outeq}}{output equation number} +\item{\code{outeq}}{output equation number} - \item{\code{block}}{dosing block number for each subject, as defined by dose resets (evid=4).} +\item{\code{block}}{dosing block number for each subject, as defined by dose resets (evid=4).} - \item{\code{obsSD}}{standard deviation of the observation based on the assay error polynomial} +\item{\code{obsSD}}{standard deviation of the observation based on the assay error polynomial} - \item{\code{d}}{prediction error, \code{pred} - \code{obs}} +\item{\code{d}}{prediction error, \code{pred} - \code{obs}} - \item{\code{ds}}{squared prediction error} +\item{\code{ds}}{squared prediction error} - \item{\code{wd}}{weighted prediction error, which is the prediction error divided by the \code{obsSD}} +\item{\code{wd}}{weighted prediction error, which is the prediction error divided by the \code{obsSD}} - \item{\code{wds}}{weighted squared prediction error} +\item{\code{wds}}{weighted squared prediction error} - \item{\code{data}}{A data frame of class \strong{PM_op_data} combining all the above fields as its columns} - } - \if{html}{\out{
}} +\item{\code{data}}{A data frame of class \strong{PM_op_data} combining all the above fields as its columns} +} +\if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ - \itemize{ - \item \href{#method-PM_op-initialize}{\code{PM_op$new()}} - \item \href{#method-PM_op-plot}{\code{PM_op$plot()}} - \item \href{#method-PM_op-summary}{\code{PM_op$summary()}} - \item \href{#method-PM_op-auc}{\code{PM_op$auc()}} - \item \href{#method-PM_op-clone}{\code{PM_op$clone()}} - } +\itemize{ +\item \href{#method-PM_op-new}{\code{PM_op$new()}} +\item \href{#method-PM_op-plot}{\code{PM_op$plot()}} +\item \href{#method-PM_op-summary}{\code{PM_op$summary()}} +\item \href{#method-PM_op-auc}{\code{PM_op$auc()}} +\item \href{#method-PM_op-clone}{\code{PM_op$clone()}} +} } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_op-initialize}{}}} -\subsection{\code{PM_op$new()}}{ - Create new object populated with observed vs. predicted data - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_op$new(PMdata = NULL, path = ".", ...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{PMdata}}{include Saved, parsed output of prior run, used when source files are not available. +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_op-new}{}}} +\subsection{Method \code{new()}}{ +Create new object populated with observed vs. predicted data +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_op$new(PMdata = NULL, path = ".", ...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{PMdata}}{include Saved, parsed output of prior run, used when source files are not available. .} - \item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. + +\item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. .} - \item{\code{...}}{Not currently used.} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - Creation of new \code{PM_op} object is automatic at the end of a run and not generally necessary + +\item{\code{...}}{Not currently used.} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +Creation of new \code{PM_op} object is automatic at the end of a run and not generally necessary for the user to do. - } } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_op-plot}{}}} -\subsection{\code{PM_op$plot()}}{ - Plot method - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_op$plot(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{plot.PM_op}} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - See \link{plot.PM_op}. - } +\subsection{Method \code{plot()}}{ +Plot method +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_op$plot(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{plot.PM_op}} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +See \link{plot.PM_op}. +} + +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_op-summary}{}}} -\subsection{\code{PM_op$summary()}}{ - Summary method - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_op$summary(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{summary.PM_op}} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - See \link{summary.PM_op}. - } +\subsection{Method \code{summary()}}{ +Summary method +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_op$summary(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{summary.PM_op}} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +See \link{summary.PM_op}. +} + +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_op-auc}{}}} -\subsection{\code{PM_op$auc()}}{ - Calculate AUC - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_op$auc(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{make_AUC}} - \item{\code{data}}{The object to use for AUC calculation} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - See \link{make_AUC} - } +\subsection{Method \code{auc()}}{ +Calculate AUC +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_op$auc(...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{make_AUC}} + +\item{\code{data}}{The object to use for AUC calculation} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +See \link{make_AUC} } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_op-clone}{}}} -\subsection{\code{PM_op$clone()}}{ - The objects of this class are cloneable with this method. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_op$clone(deep = FALSE)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{deep}}{Whether to make a deep clone.} - } - \if{html}{\out{
}} - } +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_op$clone(deep = FALSE)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} } diff --git a/man/PM_opt.Rd b/man/PM_opt.Rd index d43727d80..8a1c7ae93 100755 --- a/man/PM_opt.Rd +++ b/man/PM_opt.Rd @@ -29,43 +29,42 @@ Models Based on Minimizing Bayes Risk: Application to Voriconazole. Michael Neely } \section{Public fields}{ - \if{html}{\out{
}} - \describe{ - \item{\code{sampleTime}}{The optimal sample times, based on requested \code{type} +\if{html}{\out{
}} +\describe{ +\item{\code{sampleTime}}{The optimal sample times, based on requested \code{type} argument to \verb{$new} creation method.} - \item{\code{bayesRisk}}{Only present for MM-optimal sampling. +\item{\code{bayesRisk}}{Only present for MM-optimal sampling. The Bayesian risk of mis-classifying a subject based on the sample times. This is more useful for comparisons between sampling strategies, with minimization the goal.} - \item{\code{simdata}}{A \link{PM_sim} object with the simulated profiles} +\item{\code{simdata}}{A \link{PM_sim} object with the simulated profiles} - \item{\code{mmInt}}{A list with the \code{mmInt} values, \code{NULL} if \code{mmInt} argument +\item{\code{mmInt}}{A list with the \code{mmInt} values, \code{NULL} if \code{mmInt} argument to \verb{$new} is missing.} - } - \if{html}{\out{
}} +} +\if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ - \itemize{ - \item \href{#method-PM_opt-initialize}{\code{PM_opt$new()}} - \item \href{#method-PM_opt-plot}{\code{PM_opt$plot()}} - \item \href{#method-PM_opt-print}{\code{PM_opt$print()}} - \item \href{#method-PM_opt-clone}{\code{PM_opt$clone()}} - } +\itemize{ +\item \href{#method-PM_opt-new}{\code{PM_opt$new()}} +\item \href{#method-PM_opt-plot}{\code{PM_opt$plot()}} +\item \href{#method-PM_opt-print}{\code{PM_opt$print()}} +\item \href{#method-PM_opt-clone}{\code{PM_opt$clone()}} +} } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_opt-initialize}{}}} -\subsection{\code{PM_opt$new()}}{ - \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_opt-new}{}}} +\subsection{Method \code{new()}}{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} Determine optimal sample times which are the most informative about the model parameters. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_opt$new( +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_opt$new( poppar, model, data, @@ -76,13 +75,13 @@ model parameters. algorithm = "mm", outeq = 1, ... -)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{poppar}}{There are several choices for the population parameters. +)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{poppar}}{There are several choices for the population parameters. \itemize{ \item A \link{PM_sim} object, in which case the simulated data will be used to calculate optimal sampling times. Here \code{model} and \code{data} are not required, as they will be extracted from the \link{PM_sim} object. @@ -105,7 +104,8 @@ number of columns equal to the number of parameters. Compatible \code{model} and \code{data} objects must be specified to simulate the output profiles from the support points generated by sampling from the specified distributions. }} - \item{\code{model}}{A \link{PM_model} object, required or optional depending on the \code{poppar} argument. + +\item{\code{model}}{A \link{PM_model} object, required or optional depending on the \code{poppar} argument. \itemize{ \item When \code{poppar} is a \link{PM_sim} object, \code{model} is ignored. \item When \code{poppar} is a \link{PM_result} object, \code{model} is optional. @@ -118,7 +118,8 @@ parameter names and values in the data frame. \item When \code{poppar} is a list of weights, means, and covariance, \code{model} is mandatory and must be compatible with the parameter names and values. }} - \item{\code{data}}{A \link{PM_data} object, required or optional depending on the \code{poppar} argument. + +\item{\code{data}}{A \link{PM_data} object, required or optional depending on the \code{poppar} argument. \itemize{ \item When \code{poppar} is a \link{PM_sim} object, \code{data} is ignored. \item When \code{poppar} is a \link{PM_result} object, \code{data} is optional. If missing, the data from the \verb{$data} field of the \link{PM_result} will be used. @@ -131,9 +132,11 @@ If specified, \code{data} must be compatible with the model that is either in th For any choice of \code{data}, the value for outputs can be coded as any number(s) other than -99. The number(s) will be replaced in the simulator output with the simulated values. Good practice is to use -1 for out values to be simulated.} - \item{\code{nsamp}}{The number of MM-optimal sample times to compute; default + +\item{\code{nsamp}}{The number of MM-optimal sample times to compute; default is 1, but can be any number. Values >4 will take an exponentially longer time.} - \item{\code{weight}}{List whose names indicate the type of weighting, and + +\item{\code{weight}}{List whose names indicate the type of weighting, and values indicate the relative weight. Values should sum to 1. Names can be any of the following: \itemize{ @@ -147,7 +150,8 @@ Any combination of AUC, max, and min can be chosen. If "none" is specified, oth weight types will be ignored and the relative value will be set to 1. For example,\code{list(auc = 0.5,max = 0.5)} or \code{list(auc = 0.2, min = 0.8)}. The default is \code{list(none = 1)}.} - \item{\code{predInt}}{The interval in fractional hours for simulated predicted outputs at times other than those specified in the template \code{data}. + +\item{\code{predInt}}{The interval in fractional hours for simulated predicted outputs at times other than those specified in the template \code{data}. The default is 0.5, which means there will be simulated outputs every 30 minutes from time 0 up to the maximal time in the template file. You may also specify \code{predInt} as a vector of 3 values, e.g. \code{c(1,4,1)}, similar to the R command \link{seq}, where the @@ -156,12 +160,16 @@ step value. Outputs for times specified in the template file will also be simul To simulate outputs \emph{only} at the output times in the template data (i.e. EVID=0 events), use \code{predInt = 0}. Note that the maximum number of predictions total is 594, so the interval must be sufficiently large to accommodate this for a given number of output equations and total time to simulate over. If \code{predInt} is set so that this cap is exceeded, predictions will be truncated.} - \item{\code{mmInt}}{Specify the time intervals from which MMopt times can be selected. + +\item{\code{mmInt}}{Specify the time intervals from which MMopt times can be selected. These should only include simulated times specified by \code{predInt}.} - \item{\code{algorithm}}{Optimal sampling algorithm. Currently not modifiable and + +\item{\code{algorithm}}{Optimal sampling algorithm. Currently not modifiable and the only option is "mm".} - \item{\code{outeq}}{Output equation to optimize} - \item{\code{...}}{Other parameters to pass to \link{PM_sim}$new(). Most are not necessary, + +\item{\code{outeq}}{Output equation to optimize} + +\item{\code{...}}{Other parameters to pass to \link{PM_sim}$new(). Most are not necessary, but \code{usePost = TRUE} can be used to calculate individual MMopt times. In this case, the number of posterior distributions contained in \code{poppar$final$postPoints} needs to match the number of subjects in \code{data}. @@ -173,71 +181,67 @@ Note that the following arguments to \link{PM_sim}$new cannot be modified. \item \code{outname} is "MMsim" \item \code{combine} is \code{TRUE} }} - \item{\code{clean}}{Boolean parameter to specify whether temporary files made in the + +\item{\code{clean}}{Boolean parameter to specify whether temporary files made in the course of the simulation run should be deleted. Defaults to \code{TRUE}. This is primarily used for debugging.} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - Currently, the only option is the multiple-model optimization algorithm. - } +} +\if{html}{\out{
}} +} +\subsection{Details}{ +Currently, the only option is the multiple-model optimization algorithm. } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_opt-plot}{}}} -\subsection{\code{PM_opt$plot()}}{ - Plot method - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_opt$plot(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{plot.PM_opt}} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - See \link{plot.PM_opt}. - } +\subsection{Method \code{plot()}}{ +Plot method +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_opt$plot(...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{plot.PM_opt}} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +See \link{plot.PM_opt}. } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_opt-print}{}}} -\subsection{\code{PM_opt$print()}}{ - Print method - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_opt$print()} - \if{html}{\out{
}} - } - \subsection{Returns}{ - Prints the optimal sampling times and Bayes Risk. - } +\subsection{Method \code{print()}}{ +Print method +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_opt$print()}\if{html}{\out{
}} } +\subsection{Returns}{ +Prints the optimal sampling times and Bayes Risk. +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_opt-clone}{}}} -\subsection{\code{PM_opt$clone()}}{ - The objects of this class are cloneable with this method. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_opt$clone(deep = FALSE)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{deep}}{Whether to make a deep clone.} - } - \if{html}{\out{
}} - } +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_opt$clone(deep = FALSE)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} } diff --git a/man/PM_pop.Rd b/man/PM_pop.Rd index 58b8c6945..135c9e0b2 100755 --- a/man/PM_pop.Rd +++ b/man/PM_pop.Rd @@ -36,9 +36,9 @@ into the R console for documentation and examples. Michael Neely, Julian Otalvaro } \section{Public fields}{ - \if{html}{\out{
}} - \describe{ - \item{\code{data}}{A data frame with the following columns: +\if{html}{\out{
}} +\describe{ +\item{\code{data}}{A data frame with the following columns: \itemize{ \item \strong{id} Subject id \item \strong{time} Time of predictions in decimal hours @@ -47,131 +47,128 @@ Michael Neely, Julian Otalvaro \item \strong{pred} Predicted output for each outeq \item \strong{block} Observation blocks within subjects as defined by \emph{EVID=4} dosing events }} - } - \if{html}{\out{
}} +} +\if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ - \itemize{ - \item \href{#method-PM_pop-initialize}{\code{PM_pop$new()}} - \item \href{#method-PM_pop-plot}{\code{PM_pop$plot()}} - \item \href{#method-PM_pop-summary}{\code{PM_pop$summary()}} - \item \href{#method-PM_pop-auc}{\code{PM_pop$auc()}} - \item \href{#method-PM_pop-clone}{\code{PM_pop$clone()}} - } +\itemize{ +\item \href{#method-PM_pop-new}{\code{PM_pop$new()}} +\item \href{#method-PM_pop-plot}{\code{PM_pop$plot()}} +\item \href{#method-PM_pop-summary}{\code{PM_pop$summary()}} +\item \href{#method-PM_pop-auc}{\code{PM_pop$auc()}} +\item \href{#method-PM_pop-clone}{\code{PM_pop$clone()}} +} } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_pop-initialize}{}}} -\subsection{\code{PM_pop$new()}}{ - Create new object populated with population predicted data at +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_pop-new}{}}} +\subsection{Method \code{new()}}{ +Create new object populated with population predicted data at regular, frequent intervals - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_pop$new(PMdata = NULL, path = ".", ...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{PMdata}}{include Saved, parsed output of prior run, used when source files are not available. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_pop$new(PMdata = NULL, path = ".", ...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{PMdata}}{include Saved, parsed output of prior run, used when source files are not available. .} - \item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. + +\item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. .} - \item{\code{...}}{Not currently used.} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - Creation of new \code{PM_pop} object is automatic and not generally necessary + +\item{\code{...}}{Not currently used.} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +Creation of new \code{PM_pop} object is automatic and not generally necessary for the user to do. - } } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_pop-plot}{}}} -\subsection{\code{PM_pop$plot()}}{ - Plot method - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_pop$plot(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{plot.PM_pop}} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - See \link{plot.PM_pop}. - } +\subsection{Method \code{plot()}}{ +Plot method +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_pop$plot(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{plot.PM_pop}} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +See \link{plot.PM_pop}. +} + +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_pop-summary}{}}} -\subsection{\code{PM_pop$summary()}}{ - Summary method - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_pop$summary(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{summary.PM_pop}} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - See \link{summary.PM_pop}. - } +\subsection{Method \code{summary()}}{ +Summary method +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_pop$summary(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{summary.PM_pop}} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +See \link{summary.PM_pop}. +} + +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_pop-auc}{}}} -\subsection{\code{PM_pop$auc()}}{ - Calculate AUC - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_pop$auc(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{make_AUC}} - \item{\code{data}}{The object to use for AUC calculation} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - See \link{make_AUC} - } +\subsection{Method \code{auc()}}{ +Calculate AUC +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_pop$auc(...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{make_AUC}} + +\item{\code{data}}{The object to use for AUC calculation} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +See \link{make_AUC} } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_pop-clone}{}}} -\subsection{\code{PM_pop$clone()}}{ - The objects of this class are cloneable with this method. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_pop$clone(deep = FALSE)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{deep}}{Whether to make a deep clone.} - } - \if{html}{\out{
}} - } +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_pop$clone(deep = FALSE)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} } diff --git a/man/PM_post.Rd b/man/PM_post.Rd index b23e913db..cfff660e4 100755 --- a/man/PM_post.Rd +++ b/man/PM_post.Rd @@ -36,9 +36,9 @@ into the R console for documentation and examples. Michael Neely, Julian Otalvaro } \section{Public fields}{ - \if{html}{\out{
}} - \describe{ - \item{\code{data}}{A data frame with the following columns: +\if{html}{\out{
}} +\describe{ +\item{\code{data}}{A data frame with the following columns: \itemize{ \item \strong{id} Subject id \item \strong{time} Time of predictions in decimal hours @@ -47,131 +47,128 @@ Michael Neely, Julian Otalvaro \item \strong{pred} Predicted output for each outeq \item \strong{block} Observation blocks within subjects as defined by \emph{EVID=4} dosing events }} - } - \if{html}{\out{
}} +} +\if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ - \itemize{ - \item \href{#method-PM_post-initialize}{\code{PM_post$new()}} - \item \href{#method-PM_post-plot}{\code{PM_post$plot()}} - \item \href{#method-PM_post-summary}{\code{PM_post$summary()}} - \item \href{#method-PM_post-auc}{\code{PM_post$auc()}} - \item \href{#method-PM_post-clone}{\code{PM_post$clone()}} - } +\itemize{ +\item \href{#method-PM_post-new}{\code{PM_post$new()}} +\item \href{#method-PM_post-plot}{\code{PM_post$plot()}} +\item \href{#method-PM_post-summary}{\code{PM_post$summary()}} +\item \href{#method-PM_post-auc}{\code{PM_post$auc()}} +\item \href{#method-PM_post-clone}{\code{PM_post$clone()}} +} } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_post-initialize}{}}} -\subsection{\code{PM_post$new()}}{ - Create new object populated with Bayesian posterior predicted data at +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_post-new}{}}} +\subsection{Method \code{new()}}{ +Create new object populated with Bayesian posterior predicted data at regular, frequent intervals - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_post$new(PMdata = NULL, path = ".", ...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{PMdata}}{include Saved, parsed output of prior run, used when source files are not available. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_post$new(PMdata = NULL, path = ".", ...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{PMdata}}{include Saved, parsed output of prior run, used when source files are not available. .} - \item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. + +\item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. .} - \item{\code{...}}{Not currently used.} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - Creation of new \code{PM_post} object is automatic and not generally necessary + +\item{\code{...}}{Not currently used.} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +Creation of new \code{PM_post} object is automatic and not generally necessary for the user to do. - } } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_post-plot}{}}} -\subsection{\code{PM_post$plot()}}{ - Plot method - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_post$plot(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{plot.PM_pop}} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - See \link{plot.PM_pop}. - } +\subsection{Method \code{plot()}}{ +Plot method +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_post$plot(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{plot.PM_pop}} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +See \link{plot.PM_pop}. +} + +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_post-summary}{}}} -\subsection{\code{PM_post$summary()}}{ - Summary method - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_post$summary(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{summary.PM_pop}} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - See \link{summary.PM_post}. - } +\subsection{Method \code{summary()}}{ +Summary method +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_post$summary(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{summary.PM_pop}} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +See \link{summary.PM_post}. +} + +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_post-auc}{}}} -\subsection{\code{PM_post$auc()}}{ - Calculate AUC - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_post$auc(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{make_AUC}} - \item{\code{data}}{The object to use for AUC calculation} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - See \link{make_AUC} - } +\subsection{Method \code{auc()}}{ +Calculate AUC +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_post$auc(...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{make_AUC}} + +\item{\code{data}}{The object to use for AUC calculation} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +See \link{make_AUC} } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_post-clone}{}}} -\subsection{\code{PM_post$clone()}}{ - The objects of this class are cloneable with this method. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_post$clone(deep = FALSE)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{deep}}{Whether to make a deep clone.} - } - \if{html}{\out{
}} - } +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_post$clone(deep = FALSE)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} } diff --git a/man/PM_pri.Rd b/man/PM_pri.Rd index 4bc02366d..fccc90bce 100755 --- a/man/PM_pri.Rd +++ b/man/PM_pri.Rd @@ -10,75 +10,70 @@ This is used internally by the \code{PM_model} class. } \keyword{internal} \section{Public fields}{ - \if{html}{\out{
}} - \describe{ - \item{\code{min}}{Minimum value of the range.} +\if{html}{\out{
}} +\describe{ +\item{\code{min}}{Minimum value of the range.} - \item{\code{max}}{Maximum value of the range.} +\item{\code{max}}{Maximum value of the range.} - \item{\code{mean}}{Mean value of the range, calculated as (min + max) / 2.} +\item{\code{mean}}{Mean value of the range, calculated as (min + max) / 2.} - \item{\code{sd}}{Standard deviation of the range, calculated as (max - min) / 6.} - } - \if{html}{\out{
}} +\item{\code{sd}}{Standard deviation of the range, calculated as (max - min) / 6.} +} +\if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ - \itemize{ - \item \href{#method-PM_pri-initialize}{\code{PM_pri$new()}} - \item \href{#method-PM_pri-print}{\code{PM_pri$print()}} - \item \href{#method-PM_pri-clone}{\code{PM_pri$clone()}} - } +\itemize{ +\item \href{#method-PM_pri-new}{\code{PM_pri$new()}} +\item \href{#method-PM_pri-print}{\code{PM_pri$print()}} +\item \href{#method-PM_pri-clone}{\code{PM_pri$clone()}} +} } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_pri-initialize}{}}} -\subsection{\code{PM_pri$new()}}{ - Initialize a new range object. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_pri$new(min, max)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{min}}{Minimum value of the range.} - \item{\code{max}}{Maximum value of the range.} - } - \if{html}{\out{
}} - } +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_pri-new}{}}} +\subsection{Method \code{new()}}{ +Initialize a new range object. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_pri$new(min, max)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{min}}{Minimum value of the range.} + +\item{\code{max}}{Maximum value of the range.} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_pri-print}{}}} -\subsection{\code{PM_pri$print()}}{ - Print the range. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_pri$print()} - \if{html}{\out{
}} - } +\subsection{Method \code{print()}}{ +Print the range. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_pri$print()}\if{html}{\out{
}} } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_pri-clone}{}}} -\subsection{\code{PM_pri$clone()}}{ - The objects of this class are cloneable with this method. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_pri$clone(deep = FALSE)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{deep}}{Whether to make a deep clone.} - } - \if{html}{\out{
}} - } +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_pri$clone(deep = FALSE)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} } diff --git a/man/PM_pta.Rd b/man/PM_pta.Rd index 330261d8f..9d6f7c51f 100755 --- a/man/PM_pta.Rd +++ b/man/PM_pta.Rd @@ -24,7 +24,7 @@ appropriate regimens. \examples{ ## ------------------------------------------------ -## Method `PM_pta$new()` +## Method `PM_pta$new` ## ------------------------------------------------ \dontrun{ @@ -73,31 +73,30 @@ Michael Neely and Jan Strojil } \keyword{internal} \section{Public fields}{ - \if{html}{\out{
}} - \describe{ - \item{\code{data}}{Contains the raw results.} - } - \if{html}{\out{
}} +\if{html}{\out{
}} +\describe{ +\item{\code{data}}{Contains the raw results.} +} +\if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ - \itemize{ - \item \href{#method-PM_pta-initialize}{\code{PM_pta$new()}} - \item \href{#method-PM_pta-save}{\code{PM_pta$save()}} - \item \href{#method-PM_pta-summary}{\code{PM_pta$summary()}} - \item \href{#method-PM_pta-plot}{\code{PM_pta$plot()}} - \item \href{#method-PM_pta-load}{\code{PM_pta$load()}} - \item \href{#method-PM_pta-clone}{\code{PM_pta$clone()}} - } +\itemize{ +\item \href{#method-PM_pta-new}{\code{PM_pta$new()}} +\item \href{#method-PM_pta-save}{\code{PM_pta$save()}} +\item \href{#method-PM_pta-summary}{\code{PM_pta$summary()}} +\item \href{#method-PM_pta-plot}{\code{PM_pta$plot()}} +\item \href{#method-PM_pta-load}{\code{PM_pta$load()}} +\item \href{#method-PM_pta-clone}{\code{PM_pta$clone()}} +} } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_pta-initialize}{}}} -\subsection{\code{PM_pta$new()}}{ - Create a new \code{PM_pta} object. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_pta$new( +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_pta-new}{}}} +\subsection{Method \code{new()}}{ +Create a new \code{PM_pta} object. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_pta$new( simdata, simlabels, target, @@ -110,13 +109,13 @@ Michael Neely and Jan Strojil icen = "median", block = 1, ... -)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{simdata}}{Can be one of multiple inputs as shown in the examples below using. +)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{simdata}}{Can be one of multiple inputs as shown in the examples below using. \itemize{ \item \code{simEx} \emph{PM_sim} \item \code{simEx$data} \emph{PM_simlist} @@ -127,8 +126,10 @@ Michael Neely and Jan Strojil \item \code{NPex$data$standard_data} \emph{PM_data_data} \item \code{"simout.txt"} matches files with wildcard ability, see \link{PM_sim} }} - \item{\code{simlabels}}{Optional character vector of labels for each simulation. Default is \code{c('Regimen 1', 'Regimen 2',...)}.} - \item{\code{target}}{One of several options. + +\item{\code{simlabels}}{Optional character vector of labels for each simulation. Default is \code{c('Regimen 1', 'Regimen 2',...)}.} + +\item{\code{target}}{One of several options. \itemize{ \item A vector of pharmacodynamic targets, such as Minimum Inhibitory Concentrations (MICs), e.g. \code{c(0.25, 0.5, 1, 2, 4, 8, 16, 32)}. \item A single numerical value such as a concentration, e.g. 10. @@ -140,7 +141,8 @@ corresponding to a \code{target_type} of "max" as in this example: \code{target = list(c(0.25, 0.5, 1, 2, 4, 8, 16, 32), 10, 50)}. The first value can also be a sampled distribution made with \link{makePTAtarget}. }} - \item{\code{target_type}}{A vector of the type for each \code{target}. For any, place a minus sign + +\item{\code{target_type}}{A vector of the type for each \code{target}. For any, place a minus sign in front to make the success less than the target ratio, e.g. \code{target_type = c("min", "-min")}. Available types: \itemize{ @@ -158,33 +160,42 @@ Use "-min" to make the success less than the target ratio. This enables testing of a specific timed concentration (e.g. one hour after a dose or C1). Be sure that the time in the simulated data is used, e.g., 122 after a dose given at 120. As for the other target types, make the number negative to make the success less than the target ratio, eg -122.} - \item{\code{success}}{A vector specifying the success statistics, e.g. 0.4 for proportion time (end-start) above target, and/or 100 for max:target. + +\item{\code{success}}{A vector specifying the success statistics, e.g. 0.4 for proportion time (end-start) above target, and/or 100 for max:target. For example \code{success = 0.4} or \code{success = c(0.4, 100)}. The length must be the same as for \code{target} and \code{target_type}.} - \item{\code{outeq}}{An integer specifying the number of the simulated output equation to use. Default is 1.} - \item{\code{free_fraction}}{Proportion of free, active drug, expressed as a numeric value >=0 and <=1. Default is 1, i.e., + +\item{\code{outeq}}{An integer specifying the number of the simulated output equation to use. Default is 1.} + +\item{\code{free_fraction}}{Proportion of free, active drug, expressed as a numeric value >=0 and <=1. Default is 1, i.e., 100\% free drug or 0\% protein binding.} - \item{\code{start}}{Specify the time to begin PTA calculations. Default is a vector with the first observation time for subjects + +\item{\code{start}}{Specify the time to begin PTA calculations. Default is a vector with the first observation time for subjects in each element of \code{simdata}, e.g. dose regimen. If specified as a vector, values will be recycled as necessary.} - \item{\code{end}}{Specify the time to end PTA calculations so that PTA is calculated + +\item{\code{end}}{Specify the time to end PTA calculations so that PTA is calculated from \code{start} to \code{end}. Default for end is the maximum observation time for subjects in each element of \code{simdata}, e.g. dose regimen. If specified as a vector, values will be recycled as necessary. Subjects with insufficient data (fewer than 5 simulated observations) for a specified interval will trigger a warning. Ideally then, the simulated datset should contain sufficient observations within the interval specified by \code{start} and \code{end}.} - \item{\code{icen}}{Can be either "median" for the predictions based on medians of \code{pred.type} parameter value + +\item{\code{icen}}{Can be either "median" for the predictions based on medians of \code{pred.type} parameter value distributions, or "mean". Default is "median".} - \item{\code{block}}{Which block to plot, where a new block is defined by dose resets (evid = 4); default is 1.} - \item{\code{...}}{Not currently used} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - This function will calculate the probability of target attainment (PTA) + +\item{\code{block}}{Which block to plot, where a new block is defined by dose resets (evid = 4); default is 1.} + +\item{\code{...}}{Not currently used} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +This function will calculate the probability of target attainment (PTA) for any number of simulations, targets and definitions of success. Simulations typically differ by dose, but may differ by other features such as children vs. adults. - } - \subsection{Returns}{ - A list of class \emph{PM_pta_data}, included in the \code{data} field of the \link{PM_pta} object. +} + +\subsection{Returns}{ +A list of class \emph{PM_pta_data}, included in the \code{data} field of the \link{PM_pta} object. The list contains each \code{target_type} as an element, followed by a final \code{intersection} element showing the results for profiles which meet ALL the conditions (intersection) or \code{NA} if only one \code{target_type} was specified. @@ -209,10 +220,11 @@ Simulated (rather than discrete) targets made with \link{makePTAtarget} will be abbreviated as "(sim)", e.g. (sim)(5) for a combination of simulated targets and a single concentration target of 5. } - } - \subsection{Examples}{ - \if{html}{\out{
}} - \preformatted{pta1 <- PM_pta$new(simEx, +} +\subsection{Examples}{ +\if{html}{\out{
}} +\preformatted{\dontrun{ +pta1 <- PM_pta$new(simEx, simlabels = c("600 mg daily", "1200 mg daily", "300 mg bid", "600 mg bid"), target = list(2^(-2:6), 1, 50), target_type = c("time", 144, "-max"), @@ -246,106 +258,98 @@ pta5 <- PM_pta$new(simdata = simEx, success = c(200,1), start = 120, end = 144) } - \if{html}{\out{
}} - } } +\if{html}{\out{
}} +} + +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_pta-save}{}}} -\subsection{\code{PM_pta$save()}}{ - Save the current PM_pta object into a .rds file. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_pta$save(file_name = "PMpta.rds")} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{file_name}}{Name of the file to be created, the default is PMpta.rds} - } - \if{html}{\out{
}} - } +\subsection{Method \code{save()}}{ +Save the current PM_pta object into a .rds file. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_pta$save(file_name = "PMpta.rds")}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{file_name}}{Name of the file to be created, the default is PMpta.rds} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_pta-summary}{}}} -\subsection{\code{PM_pta$summary()}}{ - Summarize the \code{PM_pta} object. See \link{summary.PM_pta}. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_pta$summary(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{summary.PM_pta}} - } - \if{html}{\out{
}} - } +\subsection{Method \code{summary()}}{ +Summarize the \code{PM_pta} object. See \link{summary.PM_pta}. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_pta$summary(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{summary.PM_pta}} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_pta-plot}{}}} -\subsection{\code{PM_pta$plot()}}{ - Plot the \code{PM_pta} object. See \link{plot.PM_pta}. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_pta$plot(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{plot.PM_pta}} - } - \if{html}{\out{
}} - } +\subsection{Method \code{plot()}}{ +Plot the \code{PM_pta} object. See \link{plot.PM_pta}. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_pta$plot(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{plot.PM_pta}} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_pta-load}{}}} -\subsection{\code{PM_pta$load()}}{ - \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +\subsection{Method \code{load()}}{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Deprecated method to load a prior PTA Replaced by \code{PM_pta$new()} to be consistent with R6. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_pta$load(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Not used.} - } - \if{html}{\out{
}} - } +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_pta$load(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Not used.} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_pta-clone}{}}} -\subsection{\code{PM_pta$clone()}}{ - The objects of this class are cloneable with this method. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_pta$clone(deep = FALSE)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{deep}}{Whether to make a deep clone.} - } - \if{html}{\out{
}} - } +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_pta$clone(deep = FALSE)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} } diff --git a/man/PM_result.Rd b/man/PM_result.Rd index 880458d2b..e1fb85b72 100755 --- a/man/PM_result.Rd +++ b/man/PM_result.Rd @@ -19,268 +19,260 @@ Michael Neely, Julian Otalvaro } \keyword{internal} \section{Public fields}{ - \if{html}{\out{
}} - \describe{ - \item{\code{pop}}{A \link{PM_pop} object} +\if{html}{\out{
}} +\describe{ +\item{\code{pop}}{A \link{PM_pop} object} - \item{\code{post}}{A \link{PM_post} object} +\item{\code{post}}{A \link{PM_post} object} - \item{\code{final}}{A \link{PM_final} object} +\item{\code{final}}{A \link{PM_final} object} - \item{\code{cycle}}{A \link{PM_cycle} object} +\item{\code{cycle}}{A \link{PM_cycle} object} - \item{\code{op}}{A \link{PM_op} object} +\item{\code{op}}{A \link{PM_op} object} - \item{\code{cov}}{A \link{PM_cov} object} +\item{\code{cov}}{A \link{PM_cov} object} - \item{\code{data}}{\link{PM_data} object representing the original .csv data file used in the run. The predictions contained in the \verb{$data} fields from \verb{$pop} and \verb{$post} will be added to this \link{PM_data} object to permit easy addition of such predictions to raw data plots. See \link{plot.PM_data} for more details.} +\item{\code{data}}{\link{PM_data} object representing the original .csv data file used in the run. The predictions contained in the \verb{$data} fields from \verb{$pop} and \verb{$post} will be added to this \link{PM_data} object to permit easy addition of such predictions to raw data plots. See \link{plot.PM_data} for more details.} - \item{\code{model}}{text string representing the original model file used in the run} +\item{\code{model}}{text string representing the original model file used in the run} - \item{\code{errfile}}{Name of error file if it exists} +\item{\code{errfile}}{Name of error file if it exists} - \item{\code{success}}{Boolean if successful run} +\item{\code{success}}{Boolean if successful run} - \item{\code{valid}}{If the \verb{$validate} method has been executed after a run, +\item{\code{valid}}{If the \verb{$validate} method has been executed after a run, this field will contain the information required to plot visual predictive checks and normalized prediction error discrepancies via the npde code developed by Comets et al. Use the \verb{$save} method on the augmented \code{PM_result} object to save it with the new validation results.} - \item{\code{opt_samp}}{If the \verb{$opt} method has been executed after a run, this +\item{\code{opt_samp}}{If the \verb{$opt} method has been executed after a run, this field will contain a \link{PM_opt} object which has optimal sampling times and methods to plot them. Use the \verb{$save} method on the augmented \code{PM_result} object to save it with the new optimal sampling results.} - } - \if{html}{\out{
}} +} +\if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ - \itemize{ - \item \href{#method-PM_result-initialize}{\code{PM_result$new()}} - \item \href{#method-PM_result-fit}{\code{PM_result$fit()}} - \item \href{#method-PM_result-plot}{\code{PM_result$plot()}} - \item \href{#method-PM_result-summary}{\code{PM_result$summary()}} - \item \href{#method-PM_result-auc}{\code{PM_result$auc()}} - \item \href{#method-PM_result-nca}{\code{PM_result$nca()}} - \item \href{#method-PM_result-report}{\code{PM_result$report()}} - \item \href{#method-PM_result-sim}{\code{PM_result$sim()}} - \item \href{#method-PM_result-save}{\code{PM_result$save()}} - \item \href{#method-PM_result-validate}{\code{PM_result$validate()}} - \item \href{#method-PM_result-step}{\code{PM_result$step()}} - \item \href{#method-PM_result-opt}{\code{PM_result$opt()}} - \item \href{#method-PM_result-load}{\code{PM_result$load()}} - \item \href{#method-PM_result-continue}{\code{PM_result$continue()}} - \item \href{#method-PM_result-clone}{\code{PM_result$clone()}} - } +\itemize{ +\item \href{#method-PM_result-new}{\code{PM_result$new()}} +\item \href{#method-PM_result-fit}{\code{PM_result$fit()}} +\item \href{#method-PM_result-plot}{\code{PM_result$plot()}} +\item \href{#method-PM_result-summary}{\code{PM_result$summary()}} +\item \href{#method-PM_result-auc}{\code{PM_result$auc()}} +\item \href{#method-PM_result-nca}{\code{PM_result$nca()}} +\item \href{#method-PM_result-report}{\code{PM_result$report()}} +\item \href{#method-PM_result-sim}{\code{PM_result$sim()}} +\item \href{#method-PM_result-save}{\code{PM_result$save()}} +\item \href{#method-PM_result-validate}{\code{PM_result$validate()}} +\item \href{#method-PM_result-step}{\code{PM_result$step()}} +\item \href{#method-PM_result-opt}{\code{PM_result$opt()}} +\item \href{#method-PM_result-load}{\code{PM_result$load()}} +\item \href{#method-PM_result-continue}{\code{PM_result$continue()}} +\item \href{#method-PM_result-clone}{\code{PM_result$clone()}} +} } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_result-initialize}{}}} -\subsection{\code{PM_result$new()}}{ - Create new object populated with data from previous run - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_result$new(out, path = ".", quiet = TRUE)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{out}}{The parsed output from \link{PM_load}, which is +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_result-new}{}}} +\subsection{Method \code{new()}}{ +Create new object populated with data from previous run +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_result$new(out, path = ".", quiet = TRUE)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{out}}{The parsed output from \link{PM_load}, which is automatically generated. This is not a user-modifiable.} - \item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. + +\item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. .} - \item{\code{quiet}}{Quietly validate. Default is \code{FALSE}.} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - Creation of new \code{PM_result} objects is via \link{PM_load}. - } + +\item{\code{quiet}}{Quietly validate. Default is \code{FALSE}.} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +Creation of new \code{PM_result} objects is via \link{PM_load}. } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-fit}{}}} -\subsection{\code{PM_result$fit()}}{ - Fit the model to the data +\subsection{Method \code{fit()}}{ +Fit the model to the data #' @details This method is used to fit the model in the \link{PM_result} object to data. It calls the \verb{$fit} method of the model stored in the \code{model} field. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_result$fit(data, ...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{data}}{Optional data to fit. If not provided, the data stored in the +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_result$fit(data, ...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{data}}{Optional data to fit. If not provided, the data stored in the \code{data} field of the \link{PM_result} object will be used. This can be useful to continue a prior run that did not converge before the maximum number of cycles, e.g. \code{run2 <- run1$fit(cycles = 10000, prior = 1)}} - \item{\code{...}}{Additional arguments passed to the model's \verb{$fit} method.} - } - \if{html}{\out{
}} - } - \subsection{Returns}{ - Returns an invisible \link{PM_result}. - } -} +\item{\code{...}}{Additional arguments passed to the model's \verb{$fit} method.} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +Returns an invisible \link{PM_result}. +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-plot}{}}} -\subsection{\code{PM_result$plot()}}{ - Plot generic function based on type - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_result$plot(type, ...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{type}}{Type of plot based on class of object} - \item{\code{...}}{Plot-specific arguments} - } - \if{html}{\out{
}} - } +\subsection{Method \code{plot()}}{ +Plot generic function based on type +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_result$plot(type, ...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{type}}{Type of plot based on class of object} + +\item{\code{...}}{Plot-specific arguments} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-summary}{}}} -\subsection{\code{PM_result$summary()}}{ - Summary generic function based on type - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_result$summary(type, ...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{type}}{Type of summary based on class of object} - \item{\code{...}}{Summary-specific arguments} - } - \if{html}{\out{
}} - } +\subsection{Method \code{summary()}}{ +Summary generic function based on type +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_result$summary(type, ...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{type}}{Type of summary based on class of object} + +\item{\code{...}}{Summary-specific arguments} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-auc}{}}} -\subsection{\code{PM_result$auc()}}{ - AUC generic function based on type - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_result$auc(src, ...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{src}}{Source of AUC, one of "op", "pop", "post", or "sim"} - \item{\code{...}}{Summary-specific arguments} - } - \if{html}{\out{
}} - } +\subsection{Method \code{auc()}}{ +AUC generic function based on type +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_result$auc(src, ...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{src}}{Source of AUC, one of "op", "pop", "post", or "sim"} + +\item{\code{...}}{Summary-specific arguments} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-nca}{}}} -\subsection{\code{PM_result$nca()}}{ - Perform non-compartmental analysis - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_result$nca(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{make_NCA}.} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - See \link{make_NCA}. - } +\subsection{Method \code{nca()}}{ +Perform non-compartmental analysis +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_result$nca(...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{make_NCA}.} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +See \link{make_NCA}. } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-report}{}}} -\subsection{\code{PM_result$report()}}{ - Re-generate the report - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_result$report(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Parameters passed to \link{PM_report}. In particular, pay attention to \code{path}.} - } - \if{html}{\out{
}} - } +\subsection{Method \code{report()}}{ +Re-generate the report +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_result$report(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Parameters passed to \link{PM_report}. In particular, pay attention to \code{path}.} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-sim}{}}} -\subsection{\code{PM_result$sim()}}{ - Calls \link{PM_sim}. Default is to use the \verb{$final}, \verb{$model}, and \verb{$data} objects +\subsection{Method \code{sim()}}{ +Calls \link{PM_sim}. Default is to use the \verb{$final}, \verb{$model}, and \verb{$data} objects within the \link{PM_result}. It is common to supply a different \code{data} template. Occasionally it is necessary to use a different \code{model} with the \verb{$final} field, or vice versa. If all three are different, use \code{PM_sim$new()} instead. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_result$sim(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Parameters passed to \link{PM_sim}. If using the \verb{$final}, \verb{$model}, and +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_result$sim(...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Parameters passed to \link{PM_sim}. If using the \verb{$final}, \verb{$model}, and \verb{$data} fields, it is not necessary to specify these. Alternates for any of these should be specified. Other parameters for \link{PM_sim} should be passed as named arguments, e.g. \verb{$sim(include = 1:2, predInt = 1, limits = NA)}.} - } - \if{html}{\out{
}} - } } - +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-save}{}}} -\subsection{\code{PM_result$save()}}{ - Save the current PM_result object to an .Rdata file. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_result$save(run, file = "PMout.Rdata")} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{run}}{The run output folder number to save the revised result. If missing, +\subsection{Method \code{save()}}{ +Save the current PM_result object to an .Rdata file. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_result$save(run, file = "PMout.Rdata")}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{run}}{The run output folder number to save the revised result. If missing, will save in the current working directory. For example, if folder "1" is in your current working directory, specify \code{run = 1} to save the result to the "outputs" subfolder of the "1" folder.} - \item{\code{file}}{Custom file name. Default is "PMout.Rdata". If \code{run} is not specified, \code{file} + +\item{\code{file}}{Custom file name. Default is "PMout.Rdata". If \code{run} is not specified, \code{file} should be the full path and filename.} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - This is useful if you have updated the result in some way, for example you +} +\if{html}{\out{
}} +} +\subsection{Details}{ +This is useful if you have updated the result in some way, for example you have run the \verb{$validate()} method on the \code{PM_result} object, which returns an internal simulation based validation as a new \code{valid} field. To save this validation, use this \verb{$save} method. Note that unless a \code{file} name is provided, @@ -292,136 +284,126 @@ results with someone. The saved object is an .Rdata file. When loaded, it should be assigned to an R object, e.g. \code{run2 <- PM_result$new("filename")}. An equivalent statement would be \code{run2 <- PM_load(file = "filename")}. - } } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-validate}{}}} -\subsection{\code{PM_result$validate()}}{ - Validate the result by internal simulation methods. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_result$validate(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{PM_valid}.} - } - \if{html}{\out{
}} - } +\subsection{Method \code{validate()}}{ +Validate the result by internal simulation methods. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_result$validate(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{PM_valid}.} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-step}{}}} -\subsection{\code{PM_result$step()}}{ - Conduct stepwise linear regression of Bayesian posterior parameter values +\subsection{Method \code{step()}}{ +Conduct stepwise linear regression of Bayesian posterior parameter values and covariates. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_result$step(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{PM_step}.} - } - \if{html}{\out{
}} - } +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_result$step(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{PM_step}.} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-opt}{}}} -\subsection{\code{PM_result$opt()}}{ - Calculate optimal sampling times. +\subsection{Method \code{opt()}}{ +Calculate optimal sampling times. Method to compute optimal sampling times. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_result$opt(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Parameters to pass to \link{PM_opt}.} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - See \link{PM_opt} for details. - } +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_result$opt(...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Parameters to pass to \link{PM_opt}.} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +See \link{PM_opt} for details. } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-load}{}}} -\subsection{\code{PM_result$load()}}{ - \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +\subsection{Method \code{load()}}{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Deprecated method to load prior results saved with the \verb{$save} method. Replaced by \link{PM_load}. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_result$load(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Not used.} - } - \if{html}{\out{
}} - } +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_result$load(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Not used.} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-continue}{}}} -\subsection{\code{PM_result$continue()}}{ - Continue fitting the model to the data using the final population points +\subsection{Method \code{continue()}}{ +Continue fitting the model to the data using the final population points from the prior run as the prior for the new run. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_result$continue(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Additional arguments passed to the underlying \code{fit} method of the model. See the documentation for \code{fit} for details on supported parameters.} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - This method is useful if a prior run did not converge before the maximum +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_result$continue(...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Additional arguments passed to the underlying \code{fit} method of the model. See the documentation for \code{fit} for details on supported parameters.} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +This method is useful if a prior run did not converge before the maximum number of cycles. It uses the final population points from the prior run as the prior for a new run. The data and model are the same as in the prior run. - } } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-clone}{}}} -\subsection{\code{PM_result$clone()}}{ - The objects of this class are cloneable with this method. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_result$clone(deep = FALSE)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{deep}}{Whether to make a deep clone.} - } - \if{html}{\out{
}} - } +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_result$clone(deep = FALSE)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} } diff --git a/man/PM_sim.Rd b/man/PM_sim.Rd index 9c557eb0c..25c2bb518 100755 --- a/man/PM_sim.Rd +++ b/man/PM_sim.Rd @@ -21,7 +21,7 @@ can be found in the documentation for \verb{$new} method below. \examples{ ## ------------------------------------------------ -## Method `PM_sim$new()` +## Method `PM_sim$new` ## ------------------------------------------------ \dontrun{ @@ -79,9 +79,9 @@ Michael Neely } \keyword{internal} \section{Public fields}{ - \if{html}{\out{
}} - \describe{ - \item{\code{data}}{A list of class \emph{PM_sim} that contains the following elements. +\if{html}{\out{
}} +\describe{ +\item{\code{data}}{A list of class \emph{PM_sim} that contains the following elements. \itemize{ \item \strong{obs} Observations for all simulated templates as a data frame with these columns: \itemize{ @@ -126,51 +126,50 @@ this is a data frame of such matrices, one for each template in the \code{data} and an \code{id} column to identify the template in the \code{data} source. Again, this can be useful as a check against the original covariance in \code{poppar}. }} - } - \if{html}{\out{
}} +} +\if{html}{\out{
}} } \section{Active bindings}{ - \if{html}{\out{
}} - \describe{ - \item{\code{obs}}{Same as \code{obs} element in the \code{data} field.} +\if{html}{\out{
}} +\describe{ +\item{\code{obs}}{Same as \code{obs} element in the \code{data} field.} - \item{\code{amt}}{Same as \code{amt} element in the \code{data} field.} +\item{\code{amt}}{Same as \code{amt} element in the \code{data} field.} - \item{\code{parValues}}{Same as \code{parValues} element in the \code{data} field.} +\item{\code{parValues}}{Same as \code{parValues} element in the \code{data} field.} - \item{\code{totalSets}}{Same as \code{totalSets} element in the \code{data} field.} +\item{\code{totalSets}}{Same as \code{totalSets} element in the \code{data} field.} - \item{\code{totalMeans}}{Same as \code{totalMeans} element in the \code{data} field.} +\item{\code{totalMeans}}{Same as \code{totalMeans} element in the \code{data} field.} - \item{\code{totalCov}}{Same as \code{totalCov} element in the \code{data} field.} - } - \if{html}{\out{
}} +\item{\code{totalCov}}{Same as \code{totalCov} element in the \code{data} field.} +} +\if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ - \itemize{ - \item \href{#method-PM_sim-initialize}{\code{PM_sim$new()}} - \item \href{#method-PM_sim-save}{\code{PM_sim$save()}} - \item \href{#method-PM_sim-plot}{\code{PM_sim$plot()}} - \item \href{#method-PM_sim-opt}{\code{PM_sim$opt()}} - \item \href{#method-PM_sim-pta}{\code{PM_sim$pta()}} - \item \href{#method-PM_sim-auc}{\code{PM_sim$auc()}} - \item \href{#method-PM_sim-summary}{\code{PM_sim$summary()}} - \item \href{#method-PM_sim-run}{\code{PM_sim$run()}} - \item \href{#method-PM_sim-load}{\code{PM_sim$load()}} - \item \href{#method-PM_sim-clone}{\code{PM_sim$clone()}} - } +\itemize{ +\item \href{#method-PM_sim-new}{\code{PM_sim$new()}} +\item \href{#method-PM_sim-save}{\code{PM_sim$save()}} +\item \href{#method-PM_sim-plot}{\code{PM_sim$plot()}} +\item \href{#method-PM_sim-opt}{\code{PM_sim$opt()}} +\item \href{#method-PM_sim-pta}{\code{PM_sim$pta()}} +\item \href{#method-PM_sim-auc}{\code{PM_sim$auc()}} +\item \href{#method-PM_sim-summary}{\code{PM_sim$summary()}} +\item \href{#method-PM_sim-run}{\code{PM_sim$run()}} +\item \href{#method-PM_sim-load}{\code{PM_sim$load()}} +\item \href{#method-PM_sim-clone}{\code{PM_sim$clone()}} +} } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_sim-initialize}{}}} -\subsection{\code{PM_sim$new()}}{ - This function simulates outputs from given inputs and a model. +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_sim-new}{}}} +\subsection{Method \code{new()}}{ +This function simulates outputs from given inputs and a model. It can be called directly or via the \verb{$sim} method for \link{PM_result} objects. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_sim$new( +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_sim$new( poppar, model, data, @@ -187,13 +186,13 @@ or via the \verb{$sim} method for \link{PM_result} objects. makecsv = NULL, quiet = FALSE, ... -)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{poppar}}{One of five things: +)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{poppar}}{One of five things: \enumerate{ \item A \link{PM_result} object containing the final population parameter distribution from a model run, a model object, and a data object. @@ -284,13 +283,15 @@ treated as the parameters to be used for one simulation. sim1 <- PM_sim$new(poppar = poppar, model = run1$model, data = run1$data) }\if{html}{\out{
}} }} - \item{\code{model}}{Name of a suitable \link{PM_model} object or a model file template + +\item{\code{model}}{Name of a suitable \link{PM_model} object or a model file template in the working directory. If missing, and \code{poppar} is a \link{PM_result}, the model within the \verb{$model} field of the \link{PM_result} object will be used. If \code{model} is missing and \code{poppar} is not a \link{PM_result}, then Pmetrics will attempt to load a model file in the working directory called "model.txt" as the default name.} - \item{\code{data}}{Either a \link{PM_data} object or a character vector + +\item{\code{data}}{Either a \link{PM_data} object or a character vector with the file name of a Pmetrics data file in the working directory that contains \strong{template} regimens and observation times. The value for outputs can be coded as any number(s) other than -99. The @@ -301,7 +302,8 @@ the data within the \verb{$data} field of the \link{PM_result} object will be us If \code{data} is missing and \code{poppar} is not a \link{PM_result}, then Pmetrics will attempt to load a data template file in the working directory called "data.csv" as the default name.} - \item{\code{limits}}{If limits are specified, each simulated parameter set that + +\item{\code{limits}}{If limits are specified, each simulated parameter set that contains a value outside of the limits will be ignored and another set will be generated. Four options exist for limits. \itemize{ @@ -333,25 +335,30 @@ verify the simulation, but only those sets within the specified limits will be used to generate output(s) and the means and covariances of the retained sets may (and likely will be) different than those specified by \code{poppar}.} - \item{\code{split}}{Boolean operator controlling whether to split an NPAG + +\item{\code{split}}{Boolean operator controlling whether to split an NPAG \link{PM_final} object into one distribution per support point, with means equal to the vector of parameter values for that point, and covariance equal to the population covariance divided by the number of support points. Default for NPAG \link{PM_final} objects is \code{TRUE}, otherwise \code{FALSE}.} - \item{\code{include}}{A vector of subject IDs in the \code{data} to iterate + +\item{\code{include}}{A vector of subject IDs in the \code{data} to iterate through, with each subject serving as the source of an independent simulation. Default is \code{NA} and all subjects in the data file will be used.} - \item{\code{exclude}}{A vector of subject IDs to exclude in the simulation, e.g. + +\item{\code{exclude}}{A vector of subject IDs to exclude in the simulation, e.g. \code{exclude = c(4, 6:14, 16:20)}. Default is \code{NA} and all subjects in the data file will be used, i.e. none excluded. Using both \code{include} and \code{exclude} criteria may result in conflicts.} - \item{\code{nsim}}{The number of simulated profiles to create, per subject. Default + +\item{\code{nsim}}{The number of simulated profiles to create, per subject. Default is 1000. Entering 0 will result in one profile being simulated from each point in the non-parametric prior (for NPAG final objects only). If \code{poppar} is supplied as a data frame of parameter values, \code{nsim} is ignored and the number of simulated profiles will equal the number of rows in that data frame.} - \item{\code{predInt}}{The interval in fractional hours for simulated predicted + +\item{\code{predInt}}{The interval in fractional hours for simulated predicted outputs at times other than those specified in the template \code{data}. The default is 0, which means there will be simulated outputs only at times specified in the data file (see below). Values greater than 0 result in @@ -370,7 +377,8 @@ predictions is 594, so the prediction interval must be sufficiently long to accommodate this for a given number of output equations and total time to simulate over. If \code{predInt} is set so that this cap is exceeded, predictions will be truncated.} - \item{\code{covariate}}{Pmetrics can simulate values for some/all covariates + +\item{\code{covariate}}{Pmetrics can simulate values for some/all covariates declared in the \code{cov} block of the \link{PM_model}. This argument is a list with the following named elements. \itemize{ @@ -492,7 +500,8 @@ Cases 1 or 2 when age was a covariate in the data and model. A character vector (not a list) of model covariates to fix and not simulate. Values in the template data will be used and not simulated. Example: \code{covariate = list(..., fix = c("wt", "age"))}.} - \item{\code{usePost}}{Boolean, default \code{FALSE}. Only applicable when \code{poppar} contains an + +\item{\code{usePost}}{Boolean, default \code{FALSE}. Only applicable when \code{poppar} contains an NPAG \link{PM_final} object. If \code{TRUE}, the mean posterior parameter values and covariances for each subject, modified by \code{include} or \code{exclude}, @@ -502,10 +511,12 @@ Normally one uses the same data file as used to make the model final parameter distribution in \code{poppar}, but if different templates are desired, the number must be equivalent to the number of included subjects from whom the posteriors are obtained.} - \item{\code{seed}}{The seed for the random number generator. For \code{nsub} > 1, + +\item{\code{seed}}{The seed for the random number generator. For \code{nsub} > 1, should be a vector of length equal to \code{nsub}. Shorter vectors will be recycled as necessary. Default is -17.} - \item{\code{noise}}{A named list to add noise to most template data fields, + +\item{\code{noise}}{A named list to add noise to most template data fields, including covariates. The default is \code{NULL}, which means no noise will be added. The name of each element in the list should correspond to @@ -550,7 +561,8 @@ Example: out = list(c(0.1, 0.001), mode = "exp"))) }\if{html}{\out{
}} }} - \item{\code{makecsv}}{A character vector for the name of the single .csv file to be + +\item{\code{makecsv}}{A character vector for the name of the single .csv file to be made for all simulated "subjects". If no file extension is included, ".csv" will be added, e.g. "simout" will become "simout.csv". If an extension is included, Pmetrics will use it, e.g. "simout.ssv" will save under that name. @@ -560,14 +572,16 @@ be of the form \emph{id_nsim}, e.g. 1_1 through 1_10 through for the first data template id, 2_1 through 2_10 for the second template id, etc. if 10 simulations are made from each subject in the data template.} - \item{\code{quiet}}{Boolean operator controlling whether a model summary report is + +\item{\code{quiet}}{Boolean operator controlling whether a model summary report is given. Default is \code{FALSE}.} - \item{\code{...}}{Catch deprecated arguments.} - } - \if{html}{\out{}} - } - \subsection{Details}{ - The Monte Carlo simulator in Pmetrics generates randomly sampled sets of + +\item{\code{...}}{Catch deprecated arguments.} +} +\if{html}{\out{}} +} +\subsection{Details}{ +The Monte Carlo simulator in Pmetrics generates randomly sampled sets of parameters from the \emph{PRIMARY} block of a model according to a prior distribution and calculates outputs based upon a template data file. It is a powerful tool for parametric or @@ -617,13 +631,15 @@ parameter sets and corresponding observations. Simulator output is returned as a \link{PM_sim} object. Output may also be directed to a new Pmetrics .csv data file using the \code{makecsv} argument. - } - \subsection{Returns}{ - A \link{PM_sim} object. - } - \subsection{Examples}{ - \if{html}{\out{
}} - \preformatted{# Load results of NPAG run +} + +\subsection{Returns}{ +A \link{PM_sim} object. +} +\subsection{Examples}{ +\if{html}{\out{
}} +\preformatted{\dontrun{ +# Load results of NPAG run run1 <- PM_load(1) # Two methods to simulate @@ -668,193 +684,180 @@ sim2 <- mod$sim(poppar = poppar, data = "temp1.csv", nsim = 15, include = 1:4, limits = NA, noise = list(out = list(coeff = c(0.02, 0.1, 0, 0))) ) + +} + } - \if{html}{\out{
}} - } +\if{html}{\out{
}} + } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_sim-save}{}}} -\subsection{\code{PM_sim$save()}}{ - \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\subsection{Method \code{save()}}{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} Save the current PM_sim object into a .rds file. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_sim$save(file_name = "PMsim.rds")} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{file_name}}{Name of the file to be created, the default is PMsim.rds} - } - \if{html}{\out{
}} - } +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_sim$save(file_name = "PMsim.rds")}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{file_name}}{Name of the file to be created, the default is PMsim.rds} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_sim-plot}{}}} -\subsection{\code{PM_sim$plot()}}{ - \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\subsection{Method \code{plot()}}{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} Plot \code{PM_sim} object. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_sim$plot(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{plot.PM_sim}.} - \item{\code{at}}{Index of the PM_sim object to be plotted. Default is 1. -result.} - } - \if{html}{\out{
}} - } +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_sim$plot(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{plot.PM_sim}.} + +\item{\code{at}}{Index of the PM_sim object to be plotted. Default is 1. +result.} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_sim-opt}{}}} -\subsection{\code{PM_sim$opt()}}{ - \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\subsection{Method \code{opt()}}{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} Calculate optimal sampling times - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_sim$opt(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Additional parameters, refer to \link{PM_opt}} - } - \if{html}{\out{
}} - } +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_sim$opt(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Additional parameters, refer to \link{PM_opt}} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_sim-pta}{}}} -\subsection{\code{PM_sim$pta()}}{ - \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\subsection{Method \code{pta()}}{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} Estimates the Probability of Target Attaintment (PTA), based on the results of the current Simulation. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_sim$pta(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Additional parameters, refer to \link{PM_pta}.} - } - \if{html}{\out{
}} - } +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_sim$pta(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Additional parameters, refer to \link{PM_pta}.} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_sim-auc}{}}} -\subsection{\code{PM_sim$auc()}}{ - \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\subsection{Method \code{auc()}}{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} Calculates the AUC of the specified simulation - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_sim$auc(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments passed to \link{make_AUC}.} - } - \if{html}{\out{
}} - } +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_sim$auc(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments passed to \link{make_AUC}.} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_sim-summary}{}}} -\subsection{\code{PM_sim$summary()}}{ - \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\subsection{Method \code{summary()}}{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} Summarize simulation - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_sim$summary(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Parameters passed to \link{summary.PM_sim}.} - } - \if{html}{\out{
}} - } +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_sim$summary(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Parameters passed to \link{summary.PM_sim}.} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_sim-run}{}}} -\subsection{\code{PM_sim$run()}}{ - \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +\subsection{Method \code{run()}}{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Deprecated method to run a simulation. Replaced by \code{PM_sim$new()} to be consistent with R6. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_sim$run(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Not used.} - } - \if{html}{\out{
}} - } +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_sim$run(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Not used.} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_sim-load}{}}} -\subsection{\code{PM_sim$load()}}{ - \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +\subsection{Method \code{load()}}{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Deprecated method to load a prior simulation. Replaced by \code{PM_sim$new()} to be consistent with R6. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_sim$load(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Not used.} - } - \if{html}{\out{
}} - } +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_sim$load(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Not used.} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_sim-clone}{}}} -\subsection{\code{PM_sim$clone()}}{ - The objects of this class are cloneable with this method. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_sim$clone(deep = FALSE)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{deep}}{Whether to make a deep clone.} - } - \if{html}{\out{
}} - } +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_sim$clone(deep = FALSE)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} } diff --git a/man/PM_valid.Rd b/man/PM_valid.Rd index cb4aba14d..1b6d9b161 100755 --- a/man/PM_valid.Rd +++ b/man/PM_valid.Rd @@ -24,7 +24,7 @@ themselves. \examples{ ## ------------------------------------------------ -## Method `PM_valid$new()` +## Method `PM_valid$new` ## ------------------------------------------------ \dontrun{ @@ -39,73 +39,79 @@ valid <- NPex$validate(limits = c(0, 3)) Michael Neely } \section{Public fields}{ - \if{html}{\out{
}} - \describe{ - \item{\code{simdata}}{Simulated data created in the validation process} +\if{html}{\out{
}} +\describe{ +\item{\code{simdata}}{Simulated data created in the validation process} - \item{\code{timeBinMedian}}{Median times for cluster bins} +\item{\code{timeBinMedian}}{Median times for cluster bins} - \item{\code{tadBinMedian}}{Median times after previous doses for cluster bins} +\item{\code{tadBinMedian}}{Median times after previous doses for cluster bins} - \item{\code{opDF}}{Observed-predicted data frame} +\item{\code{opDF}}{Observed-predicted data frame} - \item{\code{npde}}{Data for Normalized Prediction Distribution Error} +\item{\code{npde}}{Data for Normalized Prediction Distribution Error} - \item{\code{npde_tad}}{Data for Normalized Prediction Distribution Error +\item{\code{npde_tad}}{Data for Normalized Prediction Distribution Error using Time After Dose if available} - \item{\code{npde_stats}}{Captured NPDE console summaries for later replay} - } - \if{html}{\out{
}} +\item{\code{npde_stats}}{Captured NPDE console summaries for later replay} +} +\if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ - \itemize{ - \item \href{#method-PM_valid-initialize}{\code{PM_valid$new()}} - \item \href{#method-PM_valid-show_npde_stats}{\code{PM_valid$show_npde_stats()}} - \item \href{#method-PM_valid-summary}{\code{PM_valid$summary()}} - \item \href{#method-PM_valid-plot}{\code{PM_valid$plot()}} - \item \href{#method-PM_valid-clone}{\code{PM_valid$clone()}} - } +\itemize{ +\item \href{#method-PM_valid-new}{\code{PM_valid$new()}} +\item \href{#method-PM_valid-show_npde_stats}{\code{PM_valid$show_npde_stats()}} +\item \href{#method-PM_valid-summary}{\code{PM_valid$summary()}} +\item \href{#method-PM_valid-plot}{\code{PM_valid$plot()}} +\item \href{#method-PM_valid-clone}{\code{PM_valid$clone()}} +} } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_valid-initialize}{}}} -\subsection{\code{PM_valid$new()}}{ - \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_valid-new}{}}} +\subsection{Method \code{new()}}{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} This function will create an object suitable for plotting visual predictive checks (VPCs) and prediction-corrected visual predictive checks (pcVPCs). - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_valid$new(result, tad = FALSE, binCov, doseC, timeC, tadC, limits, ...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{result}}{The result of a prior run, usually supplied by calling the +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_valid$new(result, tad = FALSE, binCov, doseC, timeC, tadC, limits, ...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{result}}{The result of a prior run, usually supplied by calling the \verb{$validate()} method of a \link{PM_result} at the end of a run, or later loaded with \link{PM_load}.} - \item{\code{tad}}{Boolean operator to use time after dose rather than time after start. Default is \code{FALSE}.} - \item{\code{binCov}}{A character vector of the names of covariates which are included in the model, i.e. in the + +\item{\code{tad}}{Boolean operator to use time after dose rather than time after start. Default is \code{FALSE}.} + +\item{\code{binCov}}{A character vector of the names of covariates which are included in the model, i.e. in the model equations and which need to be binned. For example \code{binCov='wt'} if "wt" is included in a model equation like V=V0*wt, or \code{binCov=c( 'wt', 'crcl')} if both "wt" and "crcl" are included in model equations.} - \item{\code{doseC}}{An integer with the number of dose/covariate bins to cluster, if known from a previous run of + +\item{\code{doseC}}{An integer with the number of dose/covariate bins to cluster, if known from a previous run of this function. Including this value will skip the clustering portion for doses/covariates.} - \item{\code{timeC}}{An integer with the number of observation time bins to cluster, if known from a previous run of + +\item{\code{timeC}}{An integer with the number of observation time bins to cluster, if known from a previous run of this function. Including this value will skip the clustering portion for observation times.} - \item{\code{tadC}}{An integer with the number of time after dose bins to cluster, if known from a previous run of + +\item{\code{tadC}}{An integer with the number of time after dose bins to cluster, if known from a previous run of this function. Including this value will skip the clustering portion for time after dose. This argument will be ignored if \code{tad=FALSE}.} - \item{\code{limits}}{Limits on simulated parameters. See \link{PM_sim}.} - \item{\code{...}}{Other parameters to be passed to \link{PM_sim}, especially \code{limits}.} - } - \if{html}{\out{
}} - } - \subsection{Details}{ - The function will guide the user + +\item{\code{limits}}{Limits on simulated parameters. See \link{PM_sim}.} + +\item{\code{...}}{Other parameters to be passed to \link{PM_sim}, especially \code{limits}.} +} +\if{html}{\out{
}} +} +\subsection{Details}{ +The function will guide the user through appropriate clustering of doses, covariates and sample times for prediction correction using the methods of Bergstrand et al (2011). \emph{NOTE:} Including \code{tad} is only @@ -116,9 +122,10 @@ sampling occurs after the average concentrations are the same from dose to dose. Otherwise observations are \emph{NOT} superimposable and \code{tad} should \emph{NOT} be used, i.e. should be set to \code{FALSE}. - } - \subsection{Returns}{ - An R6 object of class \code{PM_valid}, which is a list with the following. +} + +\subsection{Returns}{ +An R6 object of class \code{PM_valid}, which is a list with the following. \itemize{ \item simdata The combined, simulated files for all subjects using the population mean values and each subject as a template. This object will be automatically saved to the run, to be loaded with @@ -131,93 +138,90 @@ This will be \code{NA} if \code{tad = FALSE}. \item npde_tad NPDE with time after dose rather than absolute time, if \code{tad = TRUE} \item npde_stats Captured NPDE console summary text, replayable with \verb{$show_npde_stats()}. } - } - \subsection{Examples}{ - \if{html}{\out{
}} - \preformatted{valid <- NPex$validate(limits = c(0, 3)) } - \if{html}{\out{
}} - } +\subsection{Examples}{ +\if{html}{\out{
}} +\preformatted{\dontrun{ +valid <- NPex$validate(limits = c(0, 3)) +} + +} +\if{html}{\out{
}} + } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_valid-show_npde_stats}{}}} -\subsection{\code{PM_valid$show_npde_stats()}}{ - Replay captured NPDE summary statistics. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_valid$show_npde_stats(outeq = 1, tad = FALSE)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{outeq}}{Output equation number to replay.} - \item{\code{tad}}{Logical; replay TAD-based NPDE stats.} - } - \if{html}{\out{
}} - } +\subsection{Method \code{show_npde_stats()}}{ +Replay captured NPDE summary statistics. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_valid$show_npde_stats(outeq = 1, tad = FALSE)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{outeq}}{Output equation number to replay.} + +\item{\code{tad}}{Logical; replay TAD-based NPDE stats.} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_valid-summary}{}}} -\subsection{\code{PM_valid$summary()}}{ - Summarize a \link{PM_valid} object. Reports NPDE distribution statistics and a +\subsection{Method \code{summary()}}{ +Summarize a \link{PM_valid} object. Reports NPDE distribution statistics and a Numerical Predictive Check (NPC) for each output equation. TAD-based results are included automatically when TAD validation was performed. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_valid$summary(probs = c(0.05, 0.5, 0.95))} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{probs}}{Numeric vector of quantile cut-points for the NPC. -Default is \code{c(0.05, 0.5, 0.95)}.} - } - \if{html}{\out{
}} - } +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_valid$summary(probs = c(0.05, 0.5, 0.95))}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{probs}}{Numeric vector of quantile cut-points for the NPC. +Default is \code{c(0.05, 0.5, 0.95)}.} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_valid-plot}{}}} -\subsection{\code{PM_valid$plot()}}{ - Plot method. Calls \link{plot.PM_valid}. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_valid$plot(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{Arguments to pass to [plot.PM_valid].} - } - \if{html}{\out{
}} - } +\subsection{Method \code{plot()}}{ +Plot method. Calls \link{plot.PM_valid}. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_valid$plot(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{Arguments to pass to [plot.PM_valid].} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_valid-clone}{}}} -\subsection{\code{PM_valid$clone()}}{ - The objects of this class are cloneable with this method. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{PM_valid$clone(deep = FALSE)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{deep}}{Whether to make a deep clone.} - } - \if{html}{\out{
}} - } +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{PM_valid$clone(deep = FALSE)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} } diff --git a/man/compile_model.Rd b/man/compile_model.Rd deleted file mode 100644 index 93d827039..000000000 --- a/man/compile_model.Rd +++ /dev/null @@ -1,26 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/extendr-wrappers.R -\name{compile_model} -\alias{compile_model} -\title{Compiles the text representation of a model into a binary file.} -\usage{ -compile_model(model_path, output_path, params, template_path, kind) -} -\arguments{ -\item{model_path}{Path to the model file.} - -\item{output_path}{Path to save the compiled model.} - -\item{params}{List of model parameters.} - -\item{template_path}{Path to the template directory.} - -\item{kind}{Kind of model, which can either be "ODE" or "Analytical".} -} -\value{ -Result of the compilation process. -} -\description{ -Compiles the text representation of a model into a binary file. -} -\keyword{internal} diff --git a/man/dummy_compile.Rd b/man/dummy_compile.Rd deleted file mode 100644 index f28ad59a9..000000000 --- a/man/dummy_compile.Rd +++ /dev/null @@ -1,18 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/extendr-wrappers.R -\name{dummy_compile} -\alias{dummy_compile} -\title{Dummy function to cache compilation artifacts.} -\usage{ -dummy_compile(template_path) -} -\arguments{ -\item{template_path}{Path to the template directory.} -} -\value{ -Path to the build directory. -} -\description{ -Dummy function to cache compilation artifacts. -} -\keyword{internal} diff --git a/man/fit.Rd b/man/fit.Rd index 287a8ef22..29fbed515 100644 --- a/man/fit.Rd +++ b/man/fit.Rd @@ -2,12 +2,12 @@ % Please edit documentation in R/extendr-wrappers.R \name{fit} \alias{fit} -\title{Fits the model at the given path to the data at the given path using the provided parameters.} +\title{Fits the model DSL source to the data using the provided parameters.} \usage{ -fit(model_path, data, params, output_path, kind) +fit(model_source, data, params, output_path, kind, solver = NULL) } \arguments{ -\item{model_path}{Path to the compiled model file.} +\item{model_source}{DSL source code for the model.} \item{data}{Path to the data file.} @@ -16,11 +16,13 @@ fit(model_path, data, params, output_path, kind) \item{output_path}{Path to save the fitting results.} \item{kind}{Kind of model, which can either be "ODE" or "Analytical".} + +\item{solver}{Optional ODE solver name.} } \value{ Result of the fitting process. } \description{ -Fits the model at the given path to the data at the given path using the provided parameters. +Fits the model DSL source to the data using the provided parameters. } \keyword{internal} diff --git a/man/mic1.Rd b/man/mic1.Rd index b9e744d24..148c999b7 100755 --- a/man/mic1.Rd +++ b/man/mic1.Rd @@ -24,7 +24,7 @@ or drug, and then select the desired row of the resulting table to see a histogram (top) and table (bottom) of MIC distributions. Copy the table into excel, save as a .csv file, and read into R using a function like -\code{\link[=read.csv]{read.csv()}} or \code{\link[readr:read_csv]{readr::read_csv()}}. Then use \code{\link[=makePTAtarget]{makePTAtarget()}}. +\code{\link[=read.csv]{read.csv()}} or \code{\link[readr:read_delim]{readr::read_csv()}}. Then use \code{\link[=makePTAtarget]{makePTAtarget()}}. } \author{ Michael Neely diff --git a/man/model_parameters.Rd b/man/model_parameters.Rd index 414e4ba89..47581848c 100644 --- a/man/model_parameters.Rd +++ b/man/model_parameters.Rd @@ -2,12 +2,12 @@ % Please edit documentation in R/extendr-wrappers.R \name{model_parameters} \alias{model_parameters} -\title{Retrieves the model parameters from the compiled model at the given path.} +\title{Retrieves the model parameters from compiled DSL metadata.} \usage{ -model_parameters(model_path, kind) +model_parameters(model_source, kind) } \arguments{ -\item{model_path}{Path to the compiled model file.} +\item{model_source}{DSL source code for the model.} \item{kind}{Kind of model, which can either be "ODE" or "Analytical".} } @@ -15,6 +15,6 @@ model_parameters(model_path, kind) List of model parameters. } \description{ -Retrieves the model parameters from the compiled model at the given path. +Retrieves the model parameters from compiled DSL metadata. } \keyword{internal} diff --git a/man/plot.PM_cov.Rd b/man/plot.PM_cov.Rd index 4a9dd31a4..5ba2b2e29 100755 --- a/man/plot.PM_cov.Rd +++ b/man/plot.PM_cov.Rd @@ -171,20 +171,20 @@ NPex$cov$plot(V ~ wt, } } \seealso{ -\link{PM_cov}, \link{PM_result}, \link[plotly:schema]{schema} - -Other PMplots: -\code{\link[=plot.PM_cycle]{plot.PM_cycle()}}, -\code{\link[=plot.PM_data]{plot.PM_data()}}, -\code{\link[=plot.PM_final]{plot.PM_final()}}, -\code{\link[=plot.PM_model]{plot.PM_model()}}, -\code{\link[=plot.PM_op]{plot.PM_op()}}, -\code{\link[=plot.PM_opt]{plot.PM_opt()}}, -\code{\link[=plot.PM_pop]{plot.PM_pop()}}, -\code{\link[=plot.PM_post]{plot.PM_post()}}, -\code{\link[=plot.PM_pta]{plot.PM_pta()}}, -\code{\link[=plot.PM_sim]{plot.PM_sim()}}, -\code{\link[=plot.PM_valid]{plot.PM_valid()}} +\link{PM_cov}, \link{PM_result}, \link[plotly:schema]{plotly::schema} + +Other PMplots: +\code{\link{plot.PM_cycle}()}, +\code{\link{plot.PM_data}()}, +\code{\link{plot.PM_final}()}, +\code{\link{plot.PM_model}()}, +\code{\link{plot.PM_op}()}, +\code{\link{plot.PM_opt}()}, +\code{\link{plot.PM_pop}()}, +\code{\link{plot.PM_post}()}, +\code{\link{plot.PM_pta}()}, +\code{\link{plot.PM_sim}()}, +\code{\link{plot.PM_valid}()} } \author{ Michael Neely diff --git a/man/plot.PM_cycle.Rd b/man/plot.PM_cycle.Rd index ec2899a65..c666b6141 100755 --- a/man/plot.PM_cycle.Rd +++ b/man/plot.PM_cycle.Rd @@ -114,20 +114,20 @@ NPex$cycle$plot( } } \seealso{ -\link{PM_result}, \link[plotly:schema]{schema} +\link{PM_result}, \link[plotly:schema]{plotly::schema} -Other PMplots: -\code{\link[=plot.PM_cov]{plot.PM_cov()}}, -\code{\link[=plot.PM_data]{plot.PM_data()}}, -\code{\link[=plot.PM_final]{plot.PM_final()}}, -\code{\link[=plot.PM_model]{plot.PM_model()}}, -\code{\link[=plot.PM_op]{plot.PM_op()}}, -\code{\link[=plot.PM_opt]{plot.PM_opt()}}, -\code{\link[=plot.PM_pop]{plot.PM_pop()}}, -\code{\link[=plot.PM_post]{plot.PM_post()}}, -\code{\link[=plot.PM_pta]{plot.PM_pta()}}, -\code{\link[=plot.PM_sim]{plot.PM_sim()}}, -\code{\link[=plot.PM_valid]{plot.PM_valid()}} +Other PMplots: +\code{\link{plot.PM_cov}()}, +\code{\link{plot.PM_data}()}, +\code{\link{plot.PM_final}()}, +\code{\link{plot.PM_model}()}, +\code{\link{plot.PM_op}()}, +\code{\link{plot.PM_opt}()}, +\code{\link{plot.PM_pop}()}, +\code{\link{plot.PM_post}()}, +\code{\link{plot.PM_pta}()}, +\code{\link{plot.PM_sim}()}, +\code{\link{plot.PM_valid}()} } \author{ Michael Neely diff --git a/man/plot.PM_data.Rd b/man/plot.PM_data.Rd index 30c4dc428..4ae0fdd4e 100755 --- a/man/plot.PM_data.Rd +++ b/man/plot.PM_data.Rd @@ -208,18 +208,18 @@ dataEx$plot( \seealso{ \link{PM_data}, \link{PM_result} -Other PMplots: -\code{\link[=plot.PM_cov]{plot.PM_cov()}}, -\code{\link[=plot.PM_cycle]{plot.PM_cycle()}}, -\code{\link[=plot.PM_final]{plot.PM_final()}}, -\code{\link[=plot.PM_model]{plot.PM_model()}}, -\code{\link[=plot.PM_op]{plot.PM_op()}}, -\code{\link[=plot.PM_opt]{plot.PM_opt()}}, -\code{\link[=plot.PM_pop]{plot.PM_pop()}}, -\code{\link[=plot.PM_post]{plot.PM_post()}}, -\code{\link[=plot.PM_pta]{plot.PM_pta()}}, -\code{\link[=plot.PM_sim]{plot.PM_sim()}}, -\code{\link[=plot.PM_valid]{plot.PM_valid()}} +Other PMplots: +\code{\link{plot.PM_cov}()}, +\code{\link{plot.PM_cycle}()}, +\code{\link{plot.PM_final}()}, +\code{\link{plot.PM_model}()}, +\code{\link{plot.PM_op}()}, +\code{\link{plot.PM_opt}()}, +\code{\link{plot.PM_pop}()}, +\code{\link{plot.PM_post}()}, +\code{\link{plot.PM_pta}()}, +\code{\link{plot.PM_sim}()}, +\code{\link{plot.PM_valid}()} } \author{ Michael Neely diff --git a/man/plot.PM_final.Rd b/man/plot.PM_final.Rd index 1220e5f7c..1ee0d6e7e 100755 --- a/man/plot.PM_final.Rd +++ b/man/plot.PM_final.Rd @@ -160,20 +160,20 @@ ITex$final$plot(Ke ~ V) } } \seealso{ -\link{PM_final}, \link[plotly:schema]{schema} - -Other PMplots: -\code{\link[=plot.PM_cov]{plot.PM_cov()}}, -\code{\link[=plot.PM_cycle]{plot.PM_cycle()}}, -\code{\link[=plot.PM_data]{plot.PM_data()}}, -\code{\link[=plot.PM_model]{plot.PM_model()}}, -\code{\link[=plot.PM_op]{plot.PM_op()}}, -\code{\link[=plot.PM_opt]{plot.PM_opt()}}, -\code{\link[=plot.PM_pop]{plot.PM_pop()}}, -\code{\link[=plot.PM_post]{plot.PM_post()}}, -\code{\link[=plot.PM_pta]{plot.PM_pta()}}, -\code{\link[=plot.PM_sim]{plot.PM_sim()}}, -\code{\link[=plot.PM_valid]{plot.PM_valid()}} +\link{PM_final}, \link[plotly:schema]{plotly::schema} + +Other PMplots: +\code{\link{plot.PM_cov}()}, +\code{\link{plot.PM_cycle}()}, +\code{\link{plot.PM_data}()}, +\code{\link{plot.PM_model}()}, +\code{\link{plot.PM_op}()}, +\code{\link{plot.PM_opt}()}, +\code{\link{plot.PM_pop}()}, +\code{\link{plot.PM_post}()}, +\code{\link{plot.PM_pta}()}, +\code{\link{plot.PM_sim}()}, +\code{\link{plot.PM_valid}()} } \author{ Michael Neely diff --git a/man/plot.PM_model.Rd b/man/plot.PM_model.Rd index 0249fb452..6adc99ec2 100755 --- a/man/plot.PM_model.Rd +++ b/man/plot.PM_model.Rd @@ -74,18 +74,18 @@ NPex$model$plot() \seealso{ \link{PM_model}, \code{\link[ggplot2:ggplot]{ggplot2::ggplot()}} -Other PMplots: -\code{\link[=plot.PM_cov]{plot.PM_cov()}}, -\code{\link[=plot.PM_cycle]{plot.PM_cycle()}}, -\code{\link[=plot.PM_data]{plot.PM_data()}}, -\code{\link[=plot.PM_final]{plot.PM_final()}}, -\code{\link[=plot.PM_op]{plot.PM_op()}}, -\code{\link[=plot.PM_opt]{plot.PM_opt()}}, -\code{\link[=plot.PM_pop]{plot.PM_pop()}}, -\code{\link[=plot.PM_post]{plot.PM_post()}}, -\code{\link[=plot.PM_pta]{plot.PM_pta()}}, -\code{\link[=plot.PM_sim]{plot.PM_sim()}}, -\code{\link[=plot.PM_valid]{plot.PM_valid()}} +Other PMplots: +\code{\link{plot.PM_cov}()}, +\code{\link{plot.PM_cycle}()}, +\code{\link{plot.PM_data}()}, +\code{\link{plot.PM_final}()}, +\code{\link{plot.PM_op}()}, +\code{\link{plot.PM_opt}()}, +\code{\link{plot.PM_pop}()}, +\code{\link{plot.PM_post}()}, +\code{\link{plot.PM_pta}()}, +\code{\link{plot.PM_sim}()}, +\code{\link{plot.PM_valid}()} } \author{ Markus Hovd, Julian Otalvaro, Michael Neely diff --git a/man/plot.PM_op.Rd b/man/plot.PM_op.Rd index bf86c132f..4961763dd 100755 --- a/man/plot.PM_op.Rd +++ b/man/plot.PM_op.Rd @@ -187,20 +187,20 @@ NPex$op$plot(stats = list(x = 0.5, y = 0.2, font = list(size = 7, color = "blue" } } \seealso{ -\link{PM_result}, \link{PM_op}, \link[plotly:schema]{schema} - -Other PMplots: -\code{\link[=plot.PM_cov]{plot.PM_cov()}}, -\code{\link[=plot.PM_cycle]{plot.PM_cycle()}}, -\code{\link[=plot.PM_data]{plot.PM_data()}}, -\code{\link[=plot.PM_final]{plot.PM_final()}}, -\code{\link[=plot.PM_model]{plot.PM_model()}}, -\code{\link[=plot.PM_opt]{plot.PM_opt()}}, -\code{\link[=plot.PM_pop]{plot.PM_pop()}}, -\code{\link[=plot.PM_post]{plot.PM_post()}}, -\code{\link[=plot.PM_pta]{plot.PM_pta()}}, -\code{\link[=plot.PM_sim]{plot.PM_sim()}}, -\code{\link[=plot.PM_valid]{plot.PM_valid()}} +\link{PM_result}, \link{PM_op}, \link[plotly:schema]{plotly::schema} + +Other PMplots: +\code{\link{plot.PM_cov}()}, +\code{\link{plot.PM_cycle}()}, +\code{\link{plot.PM_data}()}, +\code{\link{plot.PM_final}()}, +\code{\link{plot.PM_model}()}, +\code{\link{plot.PM_opt}()}, +\code{\link{plot.PM_pop}()}, +\code{\link{plot.PM_post}()}, +\code{\link{plot.PM_pta}()}, +\code{\link{plot.PM_sim}()}, +\code{\link{plot.PM_valid}()} } \author{ Michael Neely diff --git a/man/plot.PM_opt.Rd b/man/plot.PM_opt.Rd index 8cc720352..8b627be62 100755 --- a/man/plot.PM_opt.Rd +++ b/man/plot.PM_opt.Rd @@ -37,18 +37,18 @@ for \link{plot.PM_sim}. \seealso{ \link{plot.PM_sim} -Other PMplots: -\code{\link[=plot.PM_cov]{plot.PM_cov()}}, -\code{\link[=plot.PM_cycle]{plot.PM_cycle()}}, -\code{\link[=plot.PM_data]{plot.PM_data()}}, -\code{\link[=plot.PM_final]{plot.PM_final()}}, -\code{\link[=plot.PM_model]{plot.PM_model()}}, -\code{\link[=plot.PM_op]{plot.PM_op()}}, -\code{\link[=plot.PM_pop]{plot.PM_pop()}}, -\code{\link[=plot.PM_post]{plot.PM_post()}}, -\code{\link[=plot.PM_pta]{plot.PM_pta()}}, -\code{\link[=plot.PM_sim]{plot.PM_sim()}}, -\code{\link[=plot.PM_valid]{plot.PM_valid()}} +Other PMplots: +\code{\link{plot.PM_cov}()}, +\code{\link{plot.PM_cycle}()}, +\code{\link{plot.PM_data}()}, +\code{\link{plot.PM_final}()}, +\code{\link{plot.PM_model}()}, +\code{\link{plot.PM_op}()}, +\code{\link{plot.PM_pop}()}, +\code{\link{plot.PM_post}()}, +\code{\link{plot.PM_pta}()}, +\code{\link{plot.PM_sim}()}, +\code{\link{plot.PM_valid}()} } \author{ Michael Neely diff --git a/man/plot.PM_pop.Rd b/man/plot.PM_pop.Rd index 2563128f5..b562173eb 100755 --- a/man/plot.PM_pop.Rd +++ b/man/plot.PM_pop.Rd @@ -152,18 +152,18 @@ NPex$pop$plot( \seealso{ \link{PM_pop}, \link{PM_result} -Other PMplots: -\code{\link[=plot.PM_cov]{plot.PM_cov()}}, -\code{\link[=plot.PM_cycle]{plot.PM_cycle()}}, -\code{\link[=plot.PM_data]{plot.PM_data()}}, -\code{\link[=plot.PM_final]{plot.PM_final()}}, -\code{\link[=plot.PM_model]{plot.PM_model()}}, -\code{\link[=plot.PM_op]{plot.PM_op()}}, -\code{\link[=plot.PM_opt]{plot.PM_opt()}}, -\code{\link[=plot.PM_post]{plot.PM_post()}}, -\code{\link[=plot.PM_pta]{plot.PM_pta()}}, -\code{\link[=plot.PM_sim]{plot.PM_sim()}}, -\code{\link[=plot.PM_valid]{plot.PM_valid()}} +Other PMplots: +\code{\link{plot.PM_cov}()}, +\code{\link{plot.PM_cycle}()}, +\code{\link{plot.PM_data}()}, +\code{\link{plot.PM_final}()}, +\code{\link{plot.PM_model}()}, +\code{\link{plot.PM_op}()}, +\code{\link{plot.PM_opt}()}, +\code{\link{plot.PM_post}()}, +\code{\link{plot.PM_pta}()}, +\code{\link{plot.PM_sim}()}, +\code{\link{plot.PM_valid}()} } \author{ Michael Neely diff --git a/man/plot.PM_post.Rd b/man/plot.PM_post.Rd index fab0d3b30..a6a929993 100755 --- a/man/plot.PM_post.Rd +++ b/man/plot.PM_post.Rd @@ -151,18 +151,18 @@ NPex$post$plot( \seealso{ \link{PM_post}, \link{PM_result} -Other PMplots: -\code{\link[=plot.PM_cov]{plot.PM_cov()}}, -\code{\link[=plot.PM_cycle]{plot.PM_cycle()}}, -\code{\link[=plot.PM_data]{plot.PM_data()}}, -\code{\link[=plot.PM_final]{plot.PM_final()}}, -\code{\link[=plot.PM_model]{plot.PM_model()}}, -\code{\link[=plot.PM_op]{plot.PM_op()}}, -\code{\link[=plot.PM_opt]{plot.PM_opt()}}, -\code{\link[=plot.PM_pop]{plot.PM_pop()}}, -\code{\link[=plot.PM_pta]{plot.PM_pta()}}, -\code{\link[=plot.PM_sim]{plot.PM_sim()}}, -\code{\link[=plot.PM_valid]{plot.PM_valid()}} +Other PMplots: +\code{\link{plot.PM_cov}()}, +\code{\link{plot.PM_cycle}()}, +\code{\link{plot.PM_data}()}, +\code{\link{plot.PM_final}()}, +\code{\link{plot.PM_model}()}, +\code{\link{plot.PM_op}()}, +\code{\link{plot.PM_opt}()}, +\code{\link{plot.PM_pop}()}, +\code{\link{plot.PM_pta}()}, +\code{\link{plot.PM_sim}()}, +\code{\link{plot.PM_valid}()} } \author{ Michael Neely diff --git a/man/plot.PM_pta.Rd b/man/plot.PM_pta.Rd index 03ce271b2..4b7e08351 100755 --- a/man/plot.PM_pta.Rd +++ b/man/plot.PM_pta.Rd @@ -61,7 +61,7 @@ the \code{line} argument should be a list of the options for group based plottin where each group corresponds to a simulated regimen. The possible elements of the \code{line} list should be exactly named: \itemize{ -\item color Maps to the \link[plotly:plot_ly]{plot_ly} \code{colors} argument to override default colors +\item color Maps to the \link[plotly:plot_ly]{plotly::plot_ly} \code{colors} argument to override default colors applied to the lines for each regimen. This can be a named palette, which can be obtained with \code{RColorBrewer::display.brewer.all()} or a vector of hexadecimal color names. One way to ensure reliable color palettes is to use the @@ -72,12 +72,12 @@ of JavaScript on the ColorBrewer website. The default is "Set1". Palettes with fewer colors than regimens will be recycled. A color can also be a character vector of color names, recycled as needed. For example, a print-friendly choice is \code{line = list(color = "black")}. -\item width Maps to the \link[plotly:plot_ly]{plot_ly} \code{width} argument to override default widths +\item width Maps to the \link[plotly:plot_ly]{plotly::plot_ly} \code{width} argument to override default widths applied to the lines for each regimen. All lines will have the same width. The default value is 2. -\item dash Maps to the \link[plotly:plot_ly]{plot_ly} \code{linetypes} argument to override default styles +\item dash Maps to the \link[plotly:plot_ly]{plotly::plot_ly} \code{linetypes} argument to override default styles applied to the lines for each regimen. If numeric, will map to \code{lty} \link{par} values. -It can also be a character vector of dash names as listed in \link[plotly:plot_ly]{plot_ly}. +It can also be a character vector of dash names as listed in \link[plotly:plot_ly]{plotly::plot_ly}. Example: \code{line = list(color = "Blues", width = 1, dash = 2)}, which will result in dotted lines (dash = 2) all with width 1 but in different shades of blue. }} @@ -97,12 +97,12 @@ where each group corresponds to a simulated regimen. The possible elements of th marker color does not need to also be specified. Even if line plotting is suppressed with \code{line = F}, the default color value of "Set1" will be applied to markers, unless specified, e.g. \code{marker = list(color = "Blues")}. -\item symbol Maps to the \link[plotly:plot_ly]{plot_ly} \code{symbols} argument to override default symbols +\item symbol Maps to the \link[plotly:plot_ly]{plotly::plot_ly} \code{symbols} argument to override default symbols applied to the markers for each regimen. If only one value is supplied for this, it will be recycled for each regimen, i.e. all will have the same symbol. See \code{plotly::schema()}, traces > scatter > attributes > marker > symbol > values for options. -\item size Maps to the \link[plotly:plot_ly]{plot_ly} \code{size} argument to override default size +\item size Maps to the \link[plotly:plot_ly]{plotly::plot_ly} \code{size} argument to override default size applied to the markers for each regimen. All markers will have the same size. The default value is 12. }} @@ -182,18 +182,18 @@ pta1$plot() } } \seealso{ -Other PMplots: -\code{\link[=plot.PM_cov]{plot.PM_cov()}}, -\code{\link[=plot.PM_cycle]{plot.PM_cycle()}}, -\code{\link[=plot.PM_data]{plot.PM_data()}}, -\code{\link[=plot.PM_final]{plot.PM_final()}}, -\code{\link[=plot.PM_model]{plot.PM_model()}}, -\code{\link[=plot.PM_op]{plot.PM_op()}}, -\code{\link[=plot.PM_opt]{plot.PM_opt()}}, -\code{\link[=plot.PM_pop]{plot.PM_pop()}}, -\code{\link[=plot.PM_post]{plot.PM_post()}}, -\code{\link[=plot.PM_sim]{plot.PM_sim()}}, -\code{\link[=plot.PM_valid]{plot.PM_valid()}} +Other PMplots: +\code{\link{plot.PM_cov}()}, +\code{\link{plot.PM_cycle}()}, +\code{\link{plot.PM_data}()}, +\code{\link{plot.PM_final}()}, +\code{\link{plot.PM_model}()}, +\code{\link{plot.PM_op}()}, +\code{\link{plot.PM_opt}()}, +\code{\link{plot.PM_pop}()}, +\code{\link{plot.PM_post}()}, +\code{\link{plot.PM_sim}()}, +\code{\link{plot.PM_valid}()} } \author{ Michael Neely diff --git a/man/plot.PM_sim.Rd b/man/plot.PM_sim.Rd index 78886fe8e..fb54fdb11 100755 --- a/man/plot.PM_sim.Rd +++ b/man/plot.PM_sim.Rd @@ -196,20 +196,20 @@ simEx$plot(log = FALSE, line = list(color = "orange")) } } \seealso{ -\link{PM_sim}, \link[plotly:plot_ly]{plot_ly}, \link[plotly:schema]{schema} - -Other PMplots: -\code{\link[=plot.PM_cov]{plot.PM_cov()}}, -\code{\link[=plot.PM_cycle]{plot.PM_cycle()}}, -\code{\link[=plot.PM_data]{plot.PM_data()}}, -\code{\link[=plot.PM_final]{plot.PM_final()}}, -\code{\link[=plot.PM_model]{plot.PM_model()}}, -\code{\link[=plot.PM_op]{plot.PM_op()}}, -\code{\link[=plot.PM_opt]{plot.PM_opt()}}, -\code{\link[=plot.PM_pop]{plot.PM_pop()}}, -\code{\link[=plot.PM_post]{plot.PM_post()}}, -\code{\link[=plot.PM_pta]{plot.PM_pta()}}, -\code{\link[=plot.PM_valid]{plot.PM_valid()}} +\link{PM_sim}, \link[plotly:plot_ly]{plotly::plot_ly}, \link[plotly:schema]{plotly::schema} + +Other PMplots: +\code{\link{plot.PM_cov}()}, +\code{\link{plot.PM_cycle}()}, +\code{\link{plot.PM_data}()}, +\code{\link{plot.PM_final}()}, +\code{\link{plot.PM_model}()}, +\code{\link{plot.PM_op}()}, +\code{\link{plot.PM_opt}()}, +\code{\link{plot.PM_pop}()}, +\code{\link{plot.PM_post}()}, +\code{\link{plot.PM_pta}()}, +\code{\link{plot.PM_valid}()} } \author{ Michael Neely diff --git a/man/plot.PM_valid.Rd b/man/plot.PM_valid.Rd index c96c67bdd..e973c3e5f 100755 --- a/man/plot.PM_valid.Rd +++ b/man/plot.PM_valid.Rd @@ -171,18 +171,18 @@ NPex$valid$plot( \seealso{ \link{make_valid} -Other PMplots: -\code{\link[=plot.PM_cov]{plot.PM_cov()}}, -\code{\link[=plot.PM_cycle]{plot.PM_cycle()}}, -\code{\link[=plot.PM_data]{plot.PM_data()}}, -\code{\link[=plot.PM_final]{plot.PM_final()}}, -\code{\link[=plot.PM_model]{plot.PM_model()}}, -\code{\link[=plot.PM_op]{plot.PM_op()}}, -\code{\link[=plot.PM_opt]{plot.PM_opt()}}, -\code{\link[=plot.PM_pop]{plot.PM_pop()}}, -\code{\link[=plot.PM_post]{plot.PM_post()}}, -\code{\link[=plot.PM_pta]{plot.PM_pta()}}, -\code{\link[=plot.PM_sim]{plot.PM_sim()}} +Other PMplots: +\code{\link{plot.PM_cov}()}, +\code{\link{plot.PM_cycle}()}, +\code{\link{plot.PM_data}()}, +\code{\link{plot.PM_final}()}, +\code{\link{plot.PM_model}()}, +\code{\link{plot.PM_op}()}, +\code{\link{plot.PM_opt}()}, +\code{\link{plot.PM_pop}()}, +\code{\link{plot.PM_post}()}, +\code{\link{plot.PM_pta}()}, +\code{\link{plot.PM_sim}()} } \author{ Michael Neely diff --git a/man/plot.PMvalid.Rd b/man/plot.PMvalid.Rd index 64541f4d3..faf056287 100755 --- a/man/plot.PMvalid.Rd +++ b/man/plot.PMvalid.Rd @@ -58,7 +58,7 @@ See \code{\link{points}} for other values of \code{pch}.} \item{data_theme}{Default is \dQuote{color}, but could be \dQuote{grey} or \dQuote{gray}.} \item{plot_theme}{Default is \code{theme_grey()} but could be any complete ggplot2 theme, e.g. -\code{\link[ggplot2:theme_minimal]{ggplot2::theme_minimal()}}.} +\code{\link[ggplot2:ggtheme]{ggplot2::theme_minimal()}}.} \item{col.obs.ci}{Color of the observation confidence interval (set by \code{lower} and \code{upper}). Default is blue.} diff --git a/man/round2.Rd b/man/round2.Rd index d2b7d9b93..7de05237f 100755 --- a/man/round2.Rd +++ b/man/round2.Rd @@ -19,6 +19,6 @@ A character string representing the rounded value with the specified number of d Rounds a numeric value to a specified number of digits for display in flextables and plots. } \details{ -Uses \link[base:format]{base::format} and \link[base:round]{base::round} to round a numeric value to a specified number of digits. +Uses \link[base:format]{base::format} and \link[base:Round]{base::round} to round a numeric value to a specified number of digits. } \keyword{internal} diff --git a/man/simulate_all.Rd b/man/simulate_all.Rd index 2a6378b9a..ebe66c79f 100644 --- a/man/simulate_all.Rd +++ b/man/simulate_all.Rd @@ -2,23 +2,25 @@ % Please edit documentation in R/extendr-wrappers.R \name{simulate_all} \alias{simulate_all} -\title{Simulates all subjects in the data set using the model at the given path.} +\title{Simulates all subjects in the data set using the model DSL source.} \usage{ -simulate_all(data_path, model_path, theta, kind) +simulate_all(data_path, model_source, theta, kind, solver = NULL) } \arguments{ \item{data_path}{Path to the data file.} -\item{model_path}{Path to the compiled model file.} +\item{model_source}{DSL source code for the model.} \item{theta}{Data frame of support points.} \item{kind}{Kind of model, which can either be "ODE" or "Analytical".} + +\item{solver}{Optional ODE solver name.} } \value{ Simulation results. } \description{ -Simulates all subjects in the data set using the model at the given path. +Simulates all subjects in the data set using the model DSL source. } \keyword{internal} diff --git a/man/simulate_one.Rd b/man/simulate_one.Rd index 3cd976465..9bdf4f5c8 100644 --- a/man/simulate_one.Rd +++ b/man/simulate_one.Rd @@ -2,23 +2,25 @@ % Please edit documentation in R/extendr-wrappers.R \name{simulate_one} \alias{simulate_one} -\title{Simulates the first subject in the data set using the model at the given path.} +\title{Simulates the first subject in the data set using the model DSL source.} \usage{ -simulate_one(data_path, model_path, spp, kind) +simulate_one(data_path, model_source, spp, kind, solver = NULL) } \arguments{ \item{data_path}{Path to the data file.} -\item{model_path}{Path to the compiled model file.} +\item{model_source}{DSL source code for the model.} \item{spp}{One support point as a numeric vector with probabiltity.} \item{kind}{Kind of model, which can either be "ODE" or "Analytical".} + +\item{solver}{Optional ODE solver name.} } \value{ Simulation results. } \description{ -Simulates the first subject in the data set using the model at the given path. +Simulates the first subject in the data set using the model DSL source. } \keyword{internal} diff --git a/man/temporary_path.Rd b/man/temporary_path.Rd deleted file mode 100644 index f3437de86..000000000 --- a/man/temporary_path.Rd +++ /dev/null @@ -1,15 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/extendr-wrappers.R -\name{temporary_path} -\alias{temporary_path} -\title{Retrieves the temporary path used for building models.} -\usage{ -temporary_path() -} -\value{ -Temporary build path. -} -\description{ -Retrieves the temporary path used for building models. -} -\keyword{internal} diff --git a/man/validate_model_source.Rd b/man/validate_model_source.Rd new file mode 100644 index 000000000..03e6f4b7a --- /dev/null +++ b/man/validate_model_source.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/extendr-wrappers.R +\name{validate_model_source} +\alias{validate_model_source} +\title{Validates DSL model source against the runtime compiler.} +\usage{ +validate_model_source(model_source, kind, solver = NULL) +} +\arguments{ +\item{model_source}{DSL source code for the model.} + +\item{kind}{Kind of model, which can either be "ODE" or "Analytical".} + +\item{solver}{Optional ODE solver name.} +} +\value{ +Result of the validation process. +} +\description{ +Validates DSL model source against the runtime compiler. +} +\keyword{internal} diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index c86e0cbfb..4ba54d883 100755 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -9,10 +9,12 @@ name = 'pm_rs' [dependencies] extendr-api = "=0.9.0" -pmcore = { version = "=0.25.2", features = ["exa"] } -libloading = "0.9" +pmcore = { version = "=0.25.2", path = "../../../PMcore-structure", features = ["dsl-jit"] } rayon = "1.10.0" anyhow = "1.0.97" tracing = "0.1.41" tracing-subscriber = "0.3.20" + +[dev-dependencies] +extendr-engine = "=0.9.0" diff --git a/src/rust/src/executor.rs b/src/rust/src/executor.rs index 906f9a0cf..789b80f0b 100755 --- a/src/rust/src/executor.rs +++ b/src/rust/src/executor.rs @@ -1,33 +1,45 @@ use crate::settings::settings; use extendr_api::List; -use pmcore::prelude::{pharmsol::exa::load::load, simulator::SubjectPredictions, Predictions, *}; - -use std::path::PathBuf; +use pmcore::prelude::{simulator::SubjectPredictions, Predictions, *}; use crate::simulation::SimulationRow; -pub(crate) fn model_parameters(model_path: PathBuf) -> Vec { - let (_lib, (_ode, meta)) = unsafe { load::(model_path) }; - meta.get_params().clone() +pub(crate) fn model_parameters(equation: &E) -> Result> { + let metadata = equation + .equation_metadata() + .ok_or_else(|| anyhow::anyhow!("runtime model metadata is required"))?; + + Ok(metadata + .parameters() + .iter() + .map(|parameter| parameter.name().to_string()) + .collect()) } -pub(crate) fn simulate( - model_path: PathBuf, +pub(crate) fn simulate( + equation: &E, subject: &Subject, - support_point: &Vec, + parameters: &Parameters, spp_index: usize, -) -> Result> { - let (_lib, (model, meta)) = unsafe { load::(model_path) }; - if meta.get_params().len() != support_point.len() { +) -> Result> +where + E: Equation + EquationMetadataSource, +{ + let metadata = equation + .equation_metadata() + .ok_or_else(|| anyhow::anyhow!("runtime model metadata is required"))?; + + if metadata.parameters().len() != parameters.as_slice().len() { return Err(anyhow::anyhow!( "Support point has {} values but model expects {} parameters", - support_point.len(), - meta.get_params().len() + parameters.as_slice().len(), + metadata.parameters().len() )); } - let predictions: SubjectPredictions = model - .estimate_predictions(subject, support_point)? + + let predictions: SubjectPredictions = equation + .estimate_predictions(subject, parameters)? .get_predictions() .into(); Ok(SimulationRow::from_subject_predictions( @@ -37,19 +49,15 @@ pub(crate) fn simulate( )) } -pub(crate) fn fit( - model_path: PathBuf, +pub(crate) fn fit( + equation: E, data: Data, params: List, - output_path: PathBuf, -) -> std::result::Result<(), anyhow::Error> { - let (_lib, (eq, meta)) = unsafe { load::(model_path) }; - let output_path_str = output_path - .to_str() - .ok_or_else(|| anyhow::anyhow!("Output path contains invalid UTF-8: {:?}", output_path))?; - let settings = settings(params, meta.get_params(), output_path_str)?; - let mut algorithm = dispatch_algorithm(settings, eq, data)?; - let mut result = algorithm.fit()?; - result.write_outputs()?; + output_path: &str, +) -> std::result::Result<(), anyhow::Error> +where + E: Equation + Clone + Send + 'static + EquationMetadataSource, +{ + let _result = settings(params, equation, data, output_path)?.fit()?; Ok(()) } diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index a2e19e856..88f3cc603 100755 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -1,245 +1,352 @@ -// mod build; - mod executor; mod logs; mod settings; mod simulation; -use anyhow::Result; +use anyhow::{anyhow, Result as AnyResult}; use extendr_api::prelude::*; +use pmcore::api::LoggingLevel; use pmcore::prelude::{ data::{read_pmetrics, Data}, - pharmsol::exa::build, - Analytical, ODE, + pharmsol::dsl::{self, CompiledRuntimeModel, RuntimeCompilationTarget}, + EquationMetadataSource, ExplicitRkTableau, OdeSolver, Parameters, SdirkTableau, }; use simulation::SimulationRow; +use std::path::Path; use std::process::Command; use tracing_subscriber::layer::SubscriberExt; -use crate::logs::RFormatLayer; +use crate::logs::{configure_logs, LogControls, RFormatLayer}; -fn validate_paths(data_path: &str, model_path: &str) -> Result<()> { - if !std::path::Path::new(data_path).exists() { - return Err(anyhow::anyhow!("Data path does not exist: {}", data_path)); - } - if !std::path::Path::new(model_path).exists() { - return Err(anyhow::anyhow!("Model path does not exist: {}", model_path)); +fn to_r_result(result: AnyResult) -> extendr_api::Result { + result.map_err(|error| extendr_api::Error::Other(error.to_string())) +} + +fn validate_data_path(data_path: &str) -> AnyResult<()> { + if !Path::new(data_path).exists() { + return Err(anyhow!("Data path does not exist: {}", data_path)); } Ok(()) } -fn read_pmetrics_for_kind(data_path: &str, kind: &str) -> Result { +fn read_pmetrics_data(data_path: &str) -> AnyResult { + read_pmetrics(data_path).map_err(|err| anyhow!("Failed to parse data: {}", err)) +} + +fn format_runtime_compile_error(error: &dsl::RuntimeError, model_source: &str) -> String { + match error + .render_diagnostic(model_source) + .filter(|message| !message.trim().is_empty()) + { + Some(diagnostic) => format!("{}\n{}", error, diagnostic), + None => error.to_string(), + } +} + +fn parse_ode_solver(solver: Option) -> AnyResult> { + match solver { + None => Ok(None), + Some(solver) if solver.trim().is_empty() => Ok(None), + Some(solver) => match solver.trim().to_ascii_uppercase().as_str() { + "BDF" => Ok(Some(OdeSolver::Bdf)), + "TRBDF2" => Ok(Some(OdeSolver::Sdirk(SdirkTableau::TrBdf2))), + "ESDIRK34" => Ok(Some(OdeSolver::Sdirk(SdirkTableau::Esdirk34))), + "TSIT45" => Ok(Some(OdeSolver::ExplicitRk(ExplicitRkTableau::Tsit45))), + other => Err(anyhow!("Unsupported ODE solver: {}", other)), + }, + } +} + +fn compiled_model_kind(model: &CompiledRuntimeModel) -> &'static str { + match model { + CompiledRuntimeModel::Ode(_) => "ode", + CompiledRuntimeModel::Analytical(_) => "analytical", + CompiledRuntimeModel::Sde(_) => "sde", + } +} + +fn apply_ode_solver( + model: CompiledRuntimeModel, + solver: Option, +) -> CompiledRuntimeModel { + match (model, solver) { + (CompiledRuntimeModel::Ode(model), Some(solver)) => { + CompiledRuntimeModel::Ode(model.with_solver(solver)) + } + (model, _) => model, + } +} + +fn compile_runtime_model( + model_source: &str, + kind: &str, + solver: Option, +) -> AnyResult { + let model_source = model_source.trim(); + if model_source.is_empty() { + return Err(anyhow!("Model source is empty")); + } + match kind { "ode" | "analytical" => {} - err => return Err(anyhow::format_err!("{} is not a supported model type", err)), + other => return Err(anyhow!("{} is not a supported model type", other)), + } + + let compiled = dsl::compile_module_source_to_runtime( + model_source, + None, + RuntimeCompilationTarget::Jit, + |_stage, _message| {}, + ) + .map_err(|error| anyhow!(format_runtime_compile_error(&error, model_source)))?; + + let actual_kind = compiled_model_kind(&compiled); + if actual_kind != kind { + return Err(anyhow!( + "Model source kind mismatch: expected {}, got {}", + kind, + actual_kind + )); + } + + let solver = parse_ode_solver(solver)?; + if solver.is_some() && actual_kind != "ode" { + return Err(anyhow!( + "ODE solver selection is only supported for ODE models" + )); } - read_pmetrics(data_path).map_err(|err| anyhow::format_err!("Failed to parse data: {}", err)) + Ok(apply_ode_solver(compiled, solver)) } -/// Simulates the first subject in the data set using the model at the given path. +fn parse_theta(matrix: RMatrix) -> AnyResult>> { + let nspp = matrix.nrows(); + let ndim = matrix.ncols(); + let real_vector = matrix + .as_real_vector() + .ok_or_else(|| anyhow!("theta matrix must contain real values"))?; + let mut theta = vec![vec![0.0; ndim]; nspp]; + for i in 0..nspp { + for j in 0..ndim { + theta[i][j] = real_vector[i + j * nspp]; + } + } + Ok(theta) +} + +#[extendr] +fn validate_model_source(model_source: &str, kind: &str, solver: Nullable) { + extendr_api::error::unwrap_or_throw_error(to_r_result( + compile_runtime_model(model_source, kind, solver.into_option()).map(|_| ()), + )) +} + +/// Simulates the first subject in the data set using the model DSL source. /// @param data_path Path to the data file. -/// @param model_path Path to the compiled model file. +/// @param model_source DSL source code for the model. /// @param spp One support point as a numeric vector with probabiltity. /// @param kind Kind of model, which can either be "ODE" or "Analytical". +/// @param solver Optional ODE solver name. /// @return Simulation results. ///@export #[extendr] fn simulate_one( data_path: &str, - model_path: &str, + model_source: &str, spp: &[f64], kind: &str, -) -> Result> { - validate_paths(data_path, model_path)?; - let data = read_pmetrics_for_kind(data_path, kind)?; - let subjects = data.subjects(); - let first_subject = subjects - .first() - .ok_or_else(|| anyhow::anyhow!("Data set contains no subjects"))?; - let rows = match kind { - "ode" => executor::simulate::(model_path.into(), first_subject, &spp.to_vec(), 0)?, - "analytical" => { - executor::simulate::(model_path.into(), first_subject, &spp.to_vec(), 0)? - } - _ => { - return Err(anyhow::format_err!( - "{} is not a supported model type", - kind - )); - } - }; - rows.into_dataframe() - .map_err(|e| anyhow::anyhow!("Failed to build data frame: {}", e)) + solver: Nullable, +) -> Dataframe { + extendr_api::error::unwrap_or_throw_error(to_r_result( + (|| -> AnyResult> { + validate_data_path(data_path)?; + let data = read_pmetrics_data(data_path)?; + let compiled_model = compile_runtime_model(model_source, kind, solver.into_option())?; + let subjects = data.subjects(); + let first_subject = subjects + .first() + .ok_or_else(|| anyhow!("Data set contains no subjects"))?; + + let rows = match compiled_model { + CompiledRuntimeModel::Ode(equation) => { + let metadata = equation + .equation_metadata() + .ok_or_else(|| anyhow!("runtime model metadata is required"))?; + let parameters = Parameters::with_model( + &equation, + metadata + .parameters() + .iter() + .zip(spp.iter()) + .map(|(parameter, value)| (parameter.name(), *value)), + )?; + executor::simulate(&equation, first_subject, ¶meters, 0)? + } + CompiledRuntimeModel::Analytical(equation) => { + let metadata = equation + .equation_metadata() + .ok_or_else(|| anyhow!("runtime model metadata is required"))?; + let parameters = Parameters::with_model( + &equation, + metadata + .parameters() + .iter() + .zip(spp.iter()) + .map(|(parameter, value)| (parameter.name(), *value)), + )?; + executor::simulate(&equation, first_subject, ¶meters, 0)? + } + CompiledRuntimeModel::Sde(_) => { + return Err(anyhow!( + "SDE models are not supported for simulation in this Pmetrics runtime" + )); + } + }; + + rows.into_dataframe() + .map_err(|error| anyhow!("Failed to build data frame: {}", error)) + })(), + )) } -/// Simulates all subjects in the data set using the model at the given path. +/// Simulates all subjects in the data set using the model DSL source. /// @param data_path Path to the data file. -/// @param model_path Path to the compiled model file. +/// @param model_source DSL source code for the model. /// @param theta Data frame of support points. /// @param kind Kind of model, which can either be "ODE" or "Analytical". +/// @param solver Optional ODE solver name. /// @return Simulation results. /// @export #[extendr] fn simulate_all( data_path: &str, - model_path: &str, + model_source: &str, theta: RMatrix, kind: &str, -) -> Result> { + solver: Nullable, +) -> Dataframe { use rayon::prelude::*; - validate_paths(data_path, model_path)?; - let theta = parse_theta(theta)?; - let data = read_pmetrics_for_kind(data_path, kind)?; - let subjects = data.subjects(); - - let rows: Vec<_> = match kind { - "ode" => theta - .par_iter() - .enumerate() - .map(|(i, spp)| { - subjects + extendr_api::error::unwrap_or_throw_error(to_r_result( + (|| -> AnyResult> { + validate_data_path(data_path)?; + let theta = parse_theta(theta)?; + let data = read_pmetrics_data(data_path)?; + let compiled_model = compile_runtime_model(model_source, kind, solver.into_option())?; + let subjects = data.subjects(); + + let rows: Vec<_> = match compiled_model { + CompiledRuntimeModel::Ode(equation) => theta .par_iter() - .map(|subject| executor::simulate::(model_path.into(), subject, spp, i)) - .collect::>>() - .map(|v| v.into_iter().flatten().collect::>()) - }) - .collect::>>()? - .into_iter() - .flatten() - .collect(), - "analytical" => theta - .par_iter() - .enumerate() - .map(|(i, spp)| { - subjects + .enumerate() + .map(|(i, spp)| { + let equation = equation.clone(); + let metadata = equation + .equation_metadata() + .ok_or_else(|| anyhow!("runtime model metadata is required"))?; + let parameters = Parameters::with_model( + &equation, + metadata + .parameters() + .iter() + .zip(spp.iter()) + .map(|(parameter, value)| (parameter.name(), *value)), + )?; + subjects + .iter() + .map(|subject| executor::simulate(&equation, subject, ¶meters, i)) + .collect::>>() + .map(|rows| rows.into_iter().flatten().collect::>()) + }) + .collect::>>()? + .into_iter() + .flatten() + .collect(), + CompiledRuntimeModel::Analytical(equation) => theta .par_iter() - .map(|subject| { - executor::simulate::(model_path.into(), subject, spp, i) + .enumerate() + .map(|(i, spp)| { + let equation = equation.clone(); + let metadata = equation + .equation_metadata() + .ok_or_else(|| anyhow!("runtime model metadata is required"))?; + let parameters = Parameters::with_model( + &equation, + metadata + .parameters() + .iter() + .zip(spp.iter()) + .map(|(parameter, value)| (parameter.name(), *value)), + )?; + subjects + .iter() + .map(|subject| executor::simulate(&equation, subject, ¶meters, i)) + .collect::>>() + .map(|rows| rows.into_iter().flatten().collect::>()) }) - .collect::>>() - .map(|v| v.into_iter().flatten().collect::>()) - }) - .collect::>>()? - .into_iter() - .flatten() - .collect(), - _ => { - return Err(anyhow::format_err!( - "{} is not a supported model type", - kind - )); - } - }; + .collect::>>()? + .into_iter() + .flatten() + .collect(), + CompiledRuntimeModel::Sde(_) => { + return Err(anyhow!( + "SDE models are not supported for simulation in this Pmetrics runtime" + )); + } + }; - rows.into_dataframe() - .map_err(|e| anyhow::anyhow!("Failed to build data frame: {}", e)) + rows.into_dataframe() + .map_err(|error| anyhow!("Failed to build data frame: {}", error)) + })(), + )) } -/// Fits the model at the given path to the data at the given path using the provided parameters. -/// @param model_path Path to the compiled model file. +/// Fits the model DSL source to the data using the provided parameters. +/// @param model_source DSL source code for the model. /// @param data Path to the data file. /// @param params List of fitting parameters. /// @param output_path Path to save the fitting results. /// @param kind Kind of model, which can either be "ODE" or "Analytical". +/// @param solver Optional ODE solver name. /// @return Result of the fitting process. /// @export #[extendr] pub fn fit( - model_path: &str, + model_source: &str, data: &str, params: List, output_path: &str, kind: &str, -) -> Result<()> { - RFormatLayer::reset_global_timer(); - setup_logs()?; - println!("Initializing model fit..."); - validate_paths(data, model_path)?; - let data = read_pmetrics_for_kind(data, kind)?; - match kind { - "ode" => executor::fit::(model_path.into(), data, params, output_path.into())?, - "analytical" => { - executor::fit::(model_path.into(), data, params, output_path.into())? - } - err => return Err(anyhow::format_err!("{} is not a supported model type", err)), - }; - Ok(()) -} + solver: Nullable, +) { + extendr_api::error::unwrap_or_throw_error(to_r_result((|| -> AnyResult<()> { + let log_controls = parse_log_controls(¶ms, output_path)?; + RFormatLayer::reset_global_timer(); + setup_logs_inner(&log_controls)?; + println!("Initializing model fit..."); -fn parse_theta(matrix: RMatrix) -> Result>> { - let nspp = matrix.nrows(); - let ndim = matrix.ncols(); - let real_vector = matrix - .as_real_vector() - .ok_or_else(|| anyhow::anyhow!("theta matrix must contain real values"))?; - let mut theta = vec![vec![0.0; ndim]; nspp]; - for i in 0..nspp { - for j in 0..ndim { - theta[i][j] = real_vector[i + j * nspp]; - } - } - Ok(theta) -} + validate_data_path(data)?; + let data = read_pmetrics_data(data)?; + let compiled_model = compile_runtime_model(model_source, kind, solver.into_option())?; -/// Compiles the text representation of a model into a binary file. -/// @param model_path Path to the model file. -/// @param output_path Path to save the compiled model. -/// @param params List of model parameters. -/// @param template_path Path to the template directory. -/// @param kind Kind of model, which can either be "ODE" or "Analytical". -/// @return Result of the compilation process. -/// @export -#[extendr] -fn compile_model( - model_path: &str, - output_path: &str, - params: Strings, - template_path: &str, - kind: &str, -) -> Result<()> { - let params: Vec = params.iter().map(|x| x.to_string()).collect(); - let model_txt = std::fs::read_to_string(model_path) - .map_err(|e| anyhow::anyhow!("Failed to read model file '{}': {}", model_path, e))?; - let template_path = std::path::PathBuf::from(template_path); - match kind { - "ode" => build::compile::( - model_txt, - Some(output_path.into()), - params.to_vec(), - template_path, - |_key, val| { - print!("{}", val); - }, - )?, - "analytical" => build::compile::( - model_txt, - Some(output_path.into()), - params.to_vec(), - template_path, - |_key, val| { - print!("{}", val); - }, - )?, - err => return Err(anyhow::format_err!("{} is not a supported model type", err)), - }; + match compiled_model { + CompiledRuntimeModel::Ode(equation) => { + executor::fit(equation, data, params, output_path)? + } + CompiledRuntimeModel::Analytical(equation) => { + executor::fit(equation, data, params, output_path)? + } + CompiledRuntimeModel::Sde(_) => { + return Err(anyhow!( + "SDE models are not supported for fitting in this Pmetrics runtime" + )); + } + } - Ok(()) + Ok(()) + })())) } -/// Dummy function to cache compilation artifacts. -/// @param template_path Path to the template directory. -/// @return Path to the build directory. -/// @export -#[extendr] -fn dummy_compile(template_path: &str) -> Result { - let template_path = std::path::PathBuf::from(template_path); - let build_path = build::dummy_compile(template_path, |_key, val| { - print!("{}", val); - })?; - Ok(build_path) -} /// Checks if Cargo is installed on the system. /// @return TRUE if Cargo is installed, FALSE otherwise. /// @export @@ -248,64 +355,107 @@ fn is_cargo_installed() -> bool { Command::new("cargo").arg("--version").output().is_ok() } -/// Retrieves the model parameters from the compiled model at the given path. -/// @param model_path Path to the compiled model file. +/// Retrieves the model parameters from compiled DSL metadata. +/// @param model_source DSL source code for the model. /// @param kind Kind of model, which can either be "ODE" or "Analytical". /// @return List of model parameters. /// @export #[extendr] -fn model_parameters(model_path: &str, kind: &str) -> Result> { - match kind { - "ode" => Ok(executor::model_parameters::(model_path.into())), - "analytical" => Ok(executor::model_parameters::(model_path.into())), - err => Err(anyhow::format_err!("{} is not a supported model type", err)), - } -} - -/// Retrieves the temporary path used for building models. -/// @return Temporary build path. -/// @export -#[extendr] -fn temporary_path() -> String { - build::temp_path().to_string_lossy().to_string() +fn model_parameters(model_source: &str, kind: &str) -> Vec { + extendr_api::error::unwrap_or_throw_error(to_r_result((|| -> AnyResult> { + match compile_runtime_model(model_source, kind, None)? { + CompiledRuntimeModel::Ode(equation) => executor::model_parameters(&equation), + CompiledRuntimeModel::Analytical(equation) => executor::model_parameters(&equation), + CompiledRuntimeModel::Sde(_) => Err(anyhow!( + "SDE models are not supported for parameter inspection in this Pmetrics runtime" + )), + } + })())) } /// Initialize the tracing subscriber with the custom R formatter /// @keywords internal /// @export -#[extendr] -fn setup_logs() -> anyhow::Result<()> { - use tracing::Level; +fn setup_logs_inner(controls: &LogControls) -> AnyResult<()> { use tracing_subscriber::filter::LevelFilter; - // Create a subscriber with our custom layer using the global timer - // Filter to show INFO and above (INFO, WARN, ERROR) so cycle logs are visible let subscriber = tracing_subscriber::registry() .with(RFormatLayer::new()) - .with(LevelFilter::from_level(Level::INFO)); + .with(LevelFilter::TRACE); - // Set as global default - this will fail if already set, which is fine - // We just ignore the error let _ = tracing::subscriber::set_global_default(subscriber); + configure_logs(controls)?; Ok(()) } +fn parse_log_level(value: &str) -> AnyResult { + match value.to_ascii_uppercase().as_str() { + "ERROR" => Ok(LoggingLevel::Error), + "WARN" => Ok(LoggingLevel::Warn), + "INFO" => Ok(LoggingLevel::Info), + "DEBUG" => Ok(LoggingLevel::Debug), + "TRACE" => Ok(LoggingLevel::Trace), + other => Err(anyhow!( + "Unsupported log_level '{}'. Expected one of ERROR, WARN, INFO, DEBUG, TRACE", + other + )), + } +} + +fn parse_log_controls(params: &List, output_path: &str) -> AnyResult { + let settings: std::collections::HashMap<&str, Robj> = + std::collections::HashMap::try_from(params) + .map_err(|e| anyhow!("Failed to convert settings list to map: {}", e))?; + + let stdout = match settings.get("stdout_logs") { + Some(value) => value + .as_logical() + .ok_or_else(|| anyhow!("Setting 'stdout_logs' is not logical"))? + .to_bool(), + None => true, + }; + + let write = match settings.get("write_logs") { + Some(value) => value + .as_logical() + .ok_or_else(|| anyhow!("Setting 'write_logs' is not logical"))? + .to_bool(), + None => false, + }; + + let level = match settings.get("log_level") { + Some(value) => { + let raw = value + .as_str() + .ok_or_else(|| anyhow!("Setting 'log_level' is not a string"))?; + parse_log_level(raw)? + } + None => LoggingLevel::Info, + }; + + Ok(LogControls { + level, + stdout, + write, + output_path: Some(std::path::PathBuf::from(output_path)), + }) +} + +#[extendr] +fn setup_logs() { + let controls = LogControls::default(); + extendr_api::error::unwrap_or_throw_error(to_r_result(setup_logs_inner(&controls))) +} + extendr_module! { mod Pmetrics; fn simulate_one; fn simulate_all; - fn compile_model; - fn dummy_compile; + fn validate_model_source; fn is_cargo_installed; fn fit; fn model_parameters; - fn temporary_path; fn setup_logs; } - -// To generate the exported function in R, run the following command: -// rextendr::document() -// Optional: reload Pmetrics -// devtools::load_all() diff --git a/src/rust/src/logs.rs b/src/rust/src/logs.rs index 4a85ef59b..5c2788b38 100644 --- a/src/rust/src/logs.rs +++ b/src/rust/src/logs.rs @@ -1,9 +1,14 @@ use std::fmt; +use std::fs::{self, File}; +use std::io::Write; +use std::path::PathBuf; use std::sync::{Arc, Mutex, OnceLock}; use std::time::Instant; +use anyhow::{anyhow, Result}; use extendr_api::prelude::*; use extendr_api::rprintln; +use pmcore::api::LoggingLevel; use tracing::Level; use tracing_subscriber::fmt::time::FormatTime; use tracing_subscriber::registry::LookupSpan; @@ -11,6 +16,69 @@ use tracing_subscriber::Layer; // Global timer that can be reset static GLOBAL_TIMER: OnceLock>> = OnceLock::new(); +static GLOBAL_LOG_CONFIG: OnceLock>> = OnceLock::new(); + +#[derive(Debug)] +struct LogConfig { + level: LoggingLevel, + stdout: bool, + file: Option, +} + +impl Default for LogConfig { + fn default() -> Self { + Self { + level: LoggingLevel::Info, + stdout: true, + file: None, + } + } +} + +#[derive(Clone, Debug)] +pub(crate) struct LogControls { + pub level: LoggingLevel, + pub stdout: bool, + pub write: bool, + pub output_path: Option, +} + +impl Default for LogControls { + fn default() -> Self { + Self { + level: LoggingLevel::Info, + stdout: true, + write: false, + output_path: None, + } + } +} + +fn global_log_config() -> Arc> { + GLOBAL_LOG_CONFIG + .get_or_init(|| Arc::new(Mutex::new(LogConfig::default()))) + .clone() +} + +pub(crate) fn configure_logs(controls: &LogControls) -> Result<()> { + let file = if controls.write { + let output_path = controls + .output_path + .as_ref() + .ok_or_else(|| anyhow!("log output path is required when write_logs = TRUE"))?; + fs::create_dir_all(output_path)?; + Some(File::create(output_path.join("log.txt"))?) + } else { + None + }; + + let config_handle = global_log_config(); + let mut config = config_handle.lock().unwrap(); + config.level = controls.level; + config.stdout = controls.stdout; + config.file = file; + Ok(()) +} pub struct RFormatLayer { pub timer: CompactTimestamp, @@ -84,6 +152,19 @@ where let metadata = event.metadata(); let level = metadata.level(); + let (stdout_enabled, file_enabled) = { + let config_handle = global_log_config(); + let config = config_handle.lock().unwrap(); + if !level_enabled(config.level, *level) { + return; + } + (config.stdout, config.file.is_some()) + }; + + if !stdout_enabled && !file_enabled { + return; + } + // Create a visitor to extract field values let mut visitor = FieldVisitor::default(); event.record(&mut visitor); @@ -107,6 +188,7 @@ where // Walk the span scope from root -> current and collect formatted spans. // Each non-empty span is rendered as its own bracketed group, e.g. [Cycle 6]. let mut span_str = String::new(); + let mut plain_span_str = String::new(); if let Some(scope) = ctx.event_scope(event) { for span in scope.from_root() { let label = span @@ -119,6 +201,7 @@ where } // Cyan color so the span context stands out from the message span_str.push_str(&format!(" \x1b[36m[{}]\x1b[0m", label)); + plain_span_str.push_str(&format!(" [{}]", label)); } } @@ -131,8 +214,52 @@ where ) }; + let plain_level = match *level { + Level::ERROR => "ERROR", + Level::WARN => "WARN", + Level::INFO => "INFO", + Level::DEBUG => "DEBUG", + Level::TRACE => "TRACE", + }; + + let plain_message = if visitor.fields.is_empty() { + format!( + "[{}] [{}]{} (no fields)", + time_buffer, plain_level, plain_span_str + ) + } else { + format!( + "[{}] [{}]{} {}", + time_buffer, plain_level, plain_span_str, visitor.fields + ) + }; + + if file_enabled { + let config_handle = global_log_config(); + let mut config = config_handle.lock().unwrap(); + if let Some(file) = config.file.as_mut() { + let _ = writeln!(file, "{}", plain_message); + let _ = file.flush(); + } + } + // Use rprintln to print to R console - rprintln!("{}", message); + if stdout_enabled { + rprintln!("{}", message); + } + } +} + +fn level_enabled(configured: LoggingLevel, event: Level) -> bool { + match configured { + LoggingLevel::Error => matches!(event, Level::ERROR), + LoggingLevel::Warn => matches!(event, Level::ERROR | Level::WARN), + LoggingLevel::Info => matches!(event, Level::ERROR | Level::WARN | Level::INFO), + LoggingLevel::Debug => matches!( + event, + Level::ERROR | Level::WARN | Level::INFO | Level::DEBUG + ), + LoggingLevel::Trace => true, } } diff --git a/src/rust/src/settings.rs b/src/rust/src/settings.rs index 3a94542bd..c700bb7ba 100644 --- a/src/rust/src/settings.rs +++ b/src/rust/src/settings.rs @@ -1,57 +1,185 @@ -use anyhow::{anyhow, bail, Context, Result as AnyResult}; +use anyhow::{anyhow, bail, Result as AnyResult}; use extendr_api::{Conversions, List, Robj}; +use pmcore::api::NonparametricEstimationProblemBuilder; use pmcore::prelude::*; use std::collections::HashMap; -/// Helper: get a field from the settings hashmap or return an error. fn get_field<'a>(map: &'a HashMap<&str, Robj>, key: &str) -> AnyResult<&'a Robj> { map.get(key) .ok_or_else(|| anyhow!("Missing required setting '{}'", key)) } -/// Helper: get a field as a List or error. fn get_list(map: &HashMap<&str, Robj>, key: &str) -> AnyResult { get_field(map, key)? .as_list() .ok_or_else(|| anyhow!("Setting '{}' is not a list", key)) } -/// Helper: get a field as a string or error. fn get_str(map: &HashMap<&str, Robj>, key: &str) -> AnyResult { get_field(map, key)? .as_str() .ok_or_else(|| anyhow!("Setting '{}' is not a string", key)) - .map(|s| s.to_string()) + .map(|value| value.to_string()) } -/// Helper: get a field as a real (f64), with an optional default if it cannot be coerced. fn get_real_or(map: &HashMap<&str, Robj>, key: &str, default: f64) -> AnyResult { - Ok(get_field(map, key)?.as_real().unwrap_or(default)) + match map.get(key) { + Some(value) => value + .as_real() + .ok_or_else(|| anyhow!("Setting '{}' is not numeric", key)), + None => Ok(default), + } +} + +fn get_bool_or(map: &HashMap<&str, Robj>, key: &str, default: bool) -> AnyResult { + match map.get(key) { + Some(value) => value + .as_logical() + .ok_or_else(|| anyhow!("Setting '{}' is not logical", key)) + .map(|value| value.to_bool()), + None => Ok(default), + } +} + +pub(crate) enum FitBuilder { + Npag(NonparametricEstimationProblemBuilder), + Npod(NonparametricEstimationProblemBuilder), + PostProb(NonparametricEstimationProblemBuilder), +} + +impl FitBuilder { + fn parameter(self, parameter: Parameter) -> AnyResult { + match self { + Self::Npag(builder) => Ok(Self::Npag(builder.parameter(parameter)?)), + Self::Npod(builder) => Ok(Self::Npod(builder.parameter(parameter)?)), + Self::PostProb(builder) => Ok(Self::PostProb(builder.parameter(parameter)?)), + } + } + + fn error(self, name: &str, model: AssayErrorModel) -> AnyResult { + match self { + Self::Npag(builder) => Ok(Self::Npag(builder.error(name, model)?)), + Self::Npod(builder) => Ok(Self::Npod(builder.error(name, model)?)), + Self::PostProb(builder) => Ok(Self::PostProb(builder.error(name, model)?)), + } + } + + fn output_dir(self, path: impl Into) -> Self { + let path = path.into(); + match self { + Self::Npag(builder) => Self::Npag(builder.output_dir(path.clone())), + Self::Npod(builder) => Self::Npod(builder.output_dir(path.clone())), + Self::PostProb(builder) => Self::PostProb(builder.output_dir(path)), + } + } + + fn cycles(self, cycles: usize) -> Self { + match self { + Self::Npag(builder) => Self::Npag(builder.cycles(cycles)), + Self::Npod(builder) => Self::Npod(builder.cycles(cycles)), + Self::PostProb(builder) => Self::PostProb(builder.cycles(cycles)), + } + } + + fn cache(self, enabled: bool) -> Self { + match self { + Self::Npag(builder) => Self::Npag(builder.cache(enabled)), + Self::Npod(builder) => Self::Npod(builder.cache(enabled)), + Self::PostProb(builder) => Self::PostProb(builder.cache(enabled)), + } + } + + fn progress(self, enabled: bool) -> Self { + match self { + Self::Npag(builder) => Self::Npag(builder.progress(enabled)), + Self::Npod(builder) => Self::Npod(builder.progress(enabled)), + Self::PostProb(builder) => Self::PostProb(builder.progress(enabled)), + } + } + + fn idelta(self, value: f64) -> Self { + match self { + Self::Npag(builder) => Self::Npag(builder.idelta(value)), + Self::Npod(builder) => Self::Npod(builder.idelta(value)), + Self::PostProb(builder) => Self::PostProb(builder.idelta(value)), + } + } + + fn tad(self, value: f64) -> Self { + match self { + Self::Npag(builder) => Self::Npag(builder.tad(value)), + Self::Npod(builder) => Self::Npod(builder.tad(value)), + Self::PostProb(builder) => Self::PostProb(builder.tad(value)), + } + } + + fn prior(self, prior: Prior) -> Self { + match self { + Self::Npag(builder) => Self::Npag(builder.prior(prior.clone())), + Self::Npod(builder) => Self::Npod(builder.prior(prior.clone())), + Self::PostProb(builder) => Self::PostProb(builder.prior(prior)), + } + } +} + +impl FitBuilder +where + E: Equation + Clone + Send + 'static + EquationMetadataSource, +{ + pub(crate) fn fit(self) -> AnyResult> { + match self { + Self::Npag(builder) => builder.fit(), + Self::Npod(builder) => builder.fit(), + Self::PostProb(builder) => builder.fit(), + } + } } -pub(crate) fn settings( +pub(crate) fn settings( settings: List, - params: &Vec, + equation: E, + data: Data, output_path: &str, -) -> Result { +) -> AnyResult> +where + E: Equation + Clone + Send + 'static + EquationMetadataSource, +{ let settings: HashMap<&str, Robj> = HashMap::try_from(&settings) .map_err(|e| anyhow!("Failed to convert settings list to map: {}", e))?; + let metadata = equation + .equation_metadata() + .ok_or_else(|| anyhow!("runtime model metadata is required"))?; + let parameter_names = metadata + .parameters() + .iter() + .map(|parameter| parameter.name().to_string()) + .collect::>(); + let output_labels = metadata + .outputs() + .iter() + .map(|output| output.name().to_string()) + .collect::>(); + let ranges = get_list(&settings, "ranges")?; let ranges = robj_to_hashmap(ranges)?; - let parameters = parse_parameters(ranges, params)?; + let parameters = parse_parameters(ranges, ¶meter_names)?; let algorithm = get_str(&settings, "algorithm")?; - let algorithm = match algorithm.to_lowercase().as_str() { - "npag" => pmcore::prelude::Algorithm::NPAG, - "npod" => pmcore::prelude::Algorithm::NPOD, - "postprob" => pmcore::prelude::Algorithm::POSTPROB, + let mut builder = match algorithm.to_lowercase().as_str() { + "npag" => FitBuilder::Npag(EstimationProblem::builder(equation, data).method(Npag::new())), + "npod" => FitBuilder::Npod(EstimationProblem::builder(equation, data).method(Npod::new())), + "postprob" => { + FitBuilder::PostProb(EstimationProblem::builder(equation, data).method(PostProb::new())) + } _ => return Err(anyhow!("Algorithm {} not supported", algorithm)), }; - let error_models_raw = get_list(&settings, "error_models")?; + for parameter in parameters { + builder = builder.parameter(parameter)?; + } - let mut ems = AssayErrorModels::new().add(0, AssayErrorModel::None)?; + let error_models_raw = get_list(&settings, "error_models")?; for (i, (_, em)) in error_models_raw.iter().enumerate() { let outeq = i + 1; @@ -84,43 +212,43 @@ pub(crate) fn settings( ); } - match err_type.as_str() { + let assay_model = match err_type.as_str() { "additive" => { - ems = ems.add( - outeq, - if fixed.to_bool() { - AssayErrorModel::additive_fixed( - ErrorPoly::new(coeff[0], coeff[1], coeff[2], coeff[3]), - gamlam, - ) - } else { - AssayErrorModel::additive( - ErrorPoly::new(coeff[0], coeff[1], coeff[2], coeff[3]), - gamlam, - ) - }, - )?; + if fixed.to_bool() { + AssayErrorModel::additive_fixed( + ErrorPoly::new(coeff[0], coeff[1], coeff[2], coeff[3]), + gamlam, + ) + } else { + AssayErrorModel::additive( + ErrorPoly::new(coeff[0], coeff[1], coeff[2], coeff[3]), + gamlam, + ) + } } "proportional" => { - ems = ems.add( - outeq, - if fixed.to_bool() { - AssayErrorModel::proportional_fixed( - ErrorPoly::new(coeff[0], coeff[1], coeff[2], coeff[3]), - gamlam, - ) - } else { - AssayErrorModel::proportional( - ErrorPoly::new(coeff[0], coeff[1], coeff[2], coeff[3]), - gamlam, - ) - }, - )?; - } - err => { - bail!("Invalid Error type: {}", err); + if fixed.to_bool() { + AssayErrorModel::proportional_fixed( + ErrorPoly::new(coeff[0], coeff[1], coeff[2], coeff[3]), + gamlam, + ) + } else { + AssayErrorModel::proportional( + ErrorPoly::new(coeff[0], coeff[1], coeff[2], coeff[3]), + gamlam, + ) + } } - } + err => bail!("Invalid Error type: {}", err), + }; + + let output_name = output_labels.get(i).ok_or_else(|| { + anyhow!( + "error model {} has no matching output equation in the compiled DSL metadata", + outeq + ) + })?; + builder = builder.error(output_name, assay_model)?; } let max_cycles = get_real_or(&settings, "max_cycles", 100.0)? as usize; @@ -129,28 +257,24 @@ pub(crate) fn settings( let prior = get_str(&settings, "prior")?; let prior = match prior.as_str() { - "sobol" => pmcore::prelude::Prior::sobol(ind_points, seed), - "prior.csv" => pmcore::prelude::Prior::File("prior.csv".to_string()), + "sobol" => Prior::sobol(ind_points, seed), + "prior.csv" => Prior::File("prior.csv".to_string()), _ => return Err(anyhow!("Prior {} not supported", prior)), }; + let cache = get_bool_or(&settings, "cache", true)?; + let progress = get_bool_or(&settings, "progress", true)?; let idelta = get_real_or(&settings, "idelta", 0.01)?; let tad = get_real_or(&settings, "tad", 0.0)?; - let mut settings = Settings::builder() - .set_algorithm(algorithm) - .set_parameters(parameters) - .set_error_models(ems) - .build(); - settings.set_idelta(idelta); - settings.set_tad(tad); - settings.set_cycles(max_cycles); - settings.set_prior(prior); - settings.set_output_path(output_path.to_string()); - settings.set_write_logs(true); - settings.set_log_level(LogLevel::INFO); - settings.write().context("Failed to write settings")?; - Ok(settings) + Ok(builder + .output_dir(output_path.to_string()) + .cycles(max_cycles) + .cache(cache) + .progress(progress) + .idelta(idelta) + .tad(tad) + .prior(prior)) } fn robj_to_hashmap(list: List) -> AnyResult> { @@ -174,8 +298,8 @@ fn robj_to_hashmap(list: List) -> AnyResult> { fn parse_parameters( ranges: HashMap, params: &Vec, -) -> Result { - let mut parameters = Parameters::new(); +) -> AnyResult> { + let mut parameters = Vec::new(); for param in params.iter() { let (min, max) = match ranges.get(param) { Some(range) => range, @@ -187,8 +311,158 @@ fn parse_parameters( )); } }; - parameters = parameters.add(param.to_string(), *min, *max); + parameters.push(Parameter::bounded(param.to_string(), *min, *max)); } Ok(parameters) } + +#[cfg(test)] +mod tests { + use super::*; + use extendr_api::prelude::*; + + fn equation() -> equation::ODE { + equation::ODE::new( + |x, p, _t, dx, b, _rateiv, _cov| { + fetch_params!(p, ke); + dx[0] = -ke * x[0] + b[0]; + }, + |_p, _t, _cov| lag! {}, + |_p, _t, _cov| fa! {}, + |_p, _t, _cov, _x| {}, + |x, p, _t, _cov, y| { + fetch_params!(p, v); + y[0] = x[0] / v; + }, + ) + .with_nstates(1) + .with_ndrugs(1) + .with_nout(1) + .with_metadata( + equation::metadata::new("settings_cache") + .parameters(["ke", "v"]) + .states(["central"]) + .outputs(["0"]) + .route(equation::Route::bolus("0").to_state("central")), + ) + .expect("settings cache test metadata should validate") + } + + fn data() -> Data { + Data::new(vec![Subject::builder("1") + .bolus(0.0, 100.0, 0) + .observation(1.0, 10.0, 0) + .observation(2.0, 7.0, 0) + .build()]) + } + + fn settings_list(cache: Option, progress: Option) -> List { + let error_model = List::from_pairs(vec![ + ("initial", r!(2.0)), + ("type", r!(["additive"])), + ("fixed", r!(false)), + ("coeff", r!([0.0, 0.10, 0.0, 0.0])), + ]); + + let mut pairs = vec![ + ( + "ranges", + list!(ke = r!([0.05, 1.0]), v = r!([5.0, 50.0])).into(), + ), + ("algorithm", r!("NPAG")), + ("error_models", List::from_values(vec![error_model]).into()), + ("max_cycles", r!(2.0)), + ("prior", r!("sobol")), + ("points", r!(8.0)), + ("seed", r!(7.0)), + ]; + + if let Some(enabled) = cache { + pairs.push(("cache", r!(enabled))); + } + + if let Some(enabled) = progress { + pairs.push(("progress", r!(enabled))); + } + + List::from_pairs(pairs) + } + + fn compile_runtime_flags( + cache: Option, + progress: Option, + ) -> AnyResult<(bool, bool, bool)> { + let output_path = std::env::temp_dir() + .join("pm-rs-settings-cache") + .to_string_lossy() + .into_owned(); + let builder = settings( + settings_list(cache, progress), + equation(), + data(), + &output_path, + )?; + let problem = match builder { + FitBuilder::Npag(builder) => builder.build()?, + FitBuilder::Npod(builder) => builder.build()?, + FitBuilder::PostProb(builder) => builder.build()?, + }; + let compiled = problem.compile()?; + Ok(( + compiled.runtime_options().cache, + compiled.caches.prediction_cache_enabled, + compiled.runtime_options().progress, + )) + } + + #[test] + fn settings_respects_explicit_cache_false() { + test! { + let (runtime_cache, prediction_cache_enabled) = + { + let (runtime_cache, prediction_cache_enabled, _runtime_progress) = + compile_runtime_flags(Some(false), None) + .expect("cache = FALSE should compile"); + (runtime_cache, prediction_cache_enabled) + }; + assert!(!runtime_cache); + assert!(!prediction_cache_enabled); + } + } + + #[test] + fn settings_defaults_cache_to_true() { + test! { + let (runtime_cache, prediction_cache_enabled) = + { + let (runtime_cache, prediction_cache_enabled, _runtime_progress) = + compile_runtime_flags(None, None) + .expect("default cache settings should compile"); + (runtime_cache, prediction_cache_enabled) + }; + assert!(runtime_cache); + assert!(prediction_cache_enabled); + } + } + + #[test] + fn settings_respects_explicit_progress_false() { + test! { + let (_runtime_cache, _prediction_cache_enabled, runtime_progress) = + compile_runtime_flags(None, Some(false)) + .expect("progress = FALSE should compile"); + assert!(!runtime_progress); + } + } + + #[test] + fn settings_defaults_progress_to_true() { + test! { + let (_runtime_cache, _prediction_cache_enabled, runtime_progress) = + compile_runtime_flags(None, None) + .expect("default progress settings should compile"); + assert!(runtime_progress); + } + } +} diff --git a/tests/.dsl_runtime_validation.log b/tests/.dsl_runtime_validation.log new file mode 100644 index 000000000..a837ad1fe --- /dev/null +++ b/tests/.dsl_runtime_validation.log @@ -0,0 +1,286 @@ +✔ | F W S OK | Context + +⠏ | 0 | analytical-indexing +✔ | 1 0 | analytical-indexing + +⠏ | 0 | data-models [test] Data object creation + +⠙ | 1 1 | data-models [test] PM_data print + +⠧ | 2 6 | data-models [test] Model object creation +[test] Model can be reconstructed from an existing PM_model +[test] Current workflow: PM_model + PM_data + PM_model$fit +[test] Analytical models allow multi-line secondary conditionals + +✖ | 3 6 1 6 | data-models +──────────────────────────────────────────────────────────────────────────────── +Warning ('test-data-models.R:1:1'): (code run outside of `test_that()`) +The `code` argument to `test_that()` must be a braced expression to get accurate file-line information for failures. +Backtrace: + ▆ + 1. └─test_that(...) at test-data-models.R:1:1 + 2. └─testthat::test_that(desc, code) at tests/testthat/helper-models.R:4:3 + +Warning ('test-data-models.R:12:1'): (code run outside of `test_that()`) +The `code` argument to `test_that()` must be a braced expression to get accurate file-line information for failures. +Backtrace: + ▆ + 1. └─test_that(...) at test-data-models.R:12:1 + 2. └─testthat::test_that(desc, code) at tests/testthat/helper-models.R:4:3 + +Warning ('test-data-models.R:17:1'): (code run outside of `test_that()`) +The `code` argument to `test_that()` must be a braced expression to get accurate file-line information for failures. +Backtrace: + ▆ + 1. └─test_that(...) at test-data-models.R:17:1 + 2. └─testthat::test_that(desc, code) at tests/testthat/helper-models.R:4:3 + +Error ('test-data-models.R:18:3'): Model object creation +Error in `reserved_name_conflicts(self$arg_list)`: could not find function "reserved_name_conflicts" +Backtrace: + ▆ + 1. └─build_example_ode_model(compile = FALSE) at test-data-models.R:18:3 + 2. └─PM_model$new(...) at tests/testthat/helper-models.R:57:3 + 3. └─Pmetrics (local) initialize(...) + +Warning ('test-data-models.R:29:1'): (code run outside of `test_that()`) +The `code` argument to `test_that()` must be a braced expression to get accurate file-line information for failures. +Backtrace: + ▆ + 1. └─test_that(...) at test-data-models.R:29:1 + 2. └─testthat::test_that(desc, code) at tests/testthat/helper-models.R:4:3 + +Error ('test-data-models.R:30:3'): Model can be reconstructed from an existing PM_model +Error in `reserved_name_conflicts(self$arg_list)`: could not find function "reserved_name_conflicts" +Backtrace: + ▆ + 1. └─build_example_ode_model(compile = FALSE) at test-data-models.R:30:3 + 2. └─PM_model$new(...) at tests/testthat/helper-models.R:57:3 + 3. └─Pmetrics (local) initialize(...) + +Warning ('test-data-models.R:40:1'): (code run outside of `test_that()`) +The `code` argument to `test_that()` must be a braced expression to get accurate file-line information for failures. +Backtrace: + ▆ + 1. └─test_that(...) at test-data-models.R:40:1 + 2. └─testthat::test_that(desc, code) at tests/testthat/helper-models.R:4:3 + +Warning ('test-data-models.R:68:1'): (code run outside of `test_that()`) +The `code` argument to `test_that()` must be a braced expression to get accurate file-line information for failures. +Backtrace: + ▆ + 1. └─test_that(...) at test-data-models.R:68:1 + 2. └─testthat::test_that(desc, code) at tests/testthat/helper-models.R:4:3 + +Error ('test-data-models.R:69:3'): Analytical models allow multi-line secondary conditionals +Error in `reserved_name_conflicts(self$arg_list)`: could not find function "reserved_name_conflicts" +Backtrace: + ▆ + 1. └─PM_model$new(...) at test-data-models.R:69:3 + 2. └─Pmetrics (local) initialize(...) +──────────────────────────────────────────────────────────────────────────────── + +⠏ | 0 | pm-fit-prior-parameters [test] PM_model$fit accepts prior file with matching parameter names +[test] PM_model$fit reorders prior parameter columns to match current PRI order +[test] PM_model$fit substitutes renamed prior parameter columns when only names changed +[test] PM_model$fit rejects prior when mismatch is not a pure substitution +[test] PM_model$fit supports numeric prior run and normalizes theta.csv columns + +✖ | 5 5 0 | pm-fit-prior-parameters +──────────────────────────────────────────────────────────────────────────────── +Warning ('test-pm-fit-prior-parameters.R:62:1'): (code run outside of `test_that()`) +The `code` argument to `test_that()` must be a braced expression to get accurate file-line information for failures. +Backtrace: + ▆ + 1. └─test_that(...) at test-pm-fit-prior-parameters.R:62:1 + 2. └─testthat::test_that(desc, code) at tests/testthat/helper-models.R:4:3 + +Error ('test-pm-fit-prior-parameters.R:68:3'): PM_model$fit accepts prior file with matching parameter names +Error in `reserved_name_conflicts(self$arg_list)`: could not find function "reserved_name_conflicts" +Backtrace: + ▆ + 1. └─run_fit_with_mocked_engine(prior_df) at test-pm-fit-prior-parameters.R:68:3 + 2. └─PM_model$new(x = NPex$model, compile = FALSE) at test-pm-fit-prior-parameters.R:17:3 + 3. └─Pmetrics (local) initialize(...) + +Warning ('test-pm-fit-prior-parameters.R:80:1'): (code run outside of `test_that()`) +The `code` argument to `test_that()` must be a braced expression to get accurate file-line information for failures. +Backtrace: + ▆ + 1. └─test_that(...) at test-pm-fit-prior-parameters.R:80:1 + 2. └─testthat::test_that(desc, code) at tests/testthat/helper-models.R:4:3 + +Error ('test-pm-fit-prior-parameters.R:87:3'): PM_model$fit reorders prior parameter columns to match current PRI order +Error in `reserved_name_conflicts(self$arg_list)`: could not find function "reserved_name_conflicts" +Backtrace: + ▆ + 1. └─run_fit_with_mocked_engine(prior_df) at test-pm-fit-prior-parameters.R:87:3 + 2. └─PM_model$new(x = NPex$model, compile = FALSE) at test-pm-fit-prior-parameters.R:17:3 + 3. └─Pmetrics (local) initialize(...) + +Warning ('test-pm-fit-prior-parameters.R:95:1'): (code run outside of `test_that()`) +The `code` argument to `test_that()` must be a braced expression to get accurate file-line information for failures. +Backtrace: + ▆ + 1. └─test_that(...) at test-pm-fit-prior-parameters.R:95:1 + 2. └─testthat::test_that(desc, code) at tests/testthat/helper-models.R:4:3 + +Error ('test-pm-fit-prior-parameters.R:101:3'): PM_model$fit substitutes renamed prior parameter columns when only names changed +Error in `reserved_name_conflicts(self$arg_list)`: could not find function "reserved_name_conflicts" +Backtrace: + ▆ + 1. └─run_fit_with_mocked_engine(prior_df) at test-pm-fit-prior-parameters.R:101:3 + 2. └─PM_model$new(x = NPex$model, compile = FALSE) at test-pm-fit-prior-parameters.R:17:3 + 3. └─Pmetrics (local) initialize(...) + +Warning ('test-pm-fit-prior-parameters.R:109:1'): (code run outside of `test_that()`) +The `code` argument to `test_that()` must be a braced expression to get accurate file-line information for failures. +Backtrace: + ▆ + 1. └─test_that(...) at test-pm-fit-prior-parameters.R:109:1 + 2. └─testthat::test_that(desc, code) at tests/testthat/helper-models.R:4:3 + +Error ('test-pm-fit-prior-parameters.R:115:3'): PM_model$fit rejects prior when mismatch is not a pure substitution +Error in `reserved_name_conflicts(self$arg_list)`: could not find function "reserved_name_conflicts" +Backtrace: + ▆ + 1. └─run_fit_with_mocked_engine(prior_df) at test-pm-fit-prior-parameters.R:115:3 + 2. └─PM_model$new(x = NPex$model, compile = FALSE) at test-pm-fit-prior-parameters.R:17:3 + 3. └─Pmetrics (local) initialize(...) + +Warning ('test-pm-fit-prior-parameters.R:121:1'): (code run outside of `test_that()`) +The `code` argument to `test_that()` must be a braced expression to get accurate file-line information for failures. +Backtrace: + ▆ + 1. └─test_that(...) at test-pm-fit-prior-parameters.R:121:1 + 2. └─testthat::test_that(desc, code) at tests/testthat/helper-models.R:4:3 + +Error ('test-pm-fit-prior-parameters.R:133:3'): PM_model$fit supports numeric prior run and normalizes theta.csv columns +Error in `reserved_name_conflicts(self$arg_list)`: could not find function "reserved_name_conflicts" +Backtrace: + ▆ + 1. └─PM_model$new(x = NPex$model, compile = FALSE) at test-pm-fit-prior-parameters.R:133:3 + 2. └─Pmetrics (local) initialize(...) +──────────────────────────────────────────────────────────────────────────────── + +⠏ | 0 | pm-load-binary-fallback [test] PM_load uses saved DSL inputs instead of compiled artifacts +[test] PM_sim$new works from PM_load without compiled artifacts + +✖ | 2 2 0 | pm-load-binary-fallback +──────────────────────────────────────────────────────────────────────────────── +Warning ('test-pm-load-binary-fallback.R:3:1'): (code run outside of `test_that()`) +The `code` argument to `test_that()` must be a braced expression to get accurate file-line information for failures. +Backtrace: + ▆ + 1. └─test_that(...) at test-pm-load-binary-fallback.R:3:1 + 2. └─testthat::test_that(desc, code) at tests/testthat/helper-models.R:4:3 + +Error ('test-pm-load-binary-fallback.R:8:3'): PM_load uses saved DSL inputs instead of compiled artifacts +Error in `reserved_name_conflicts(self$arg_list)`: could not find function "reserved_name_conflicts" +Backtrace: + ▆ + 1. └─PM_model$new(x = modEx, compile = FALSE) at test-pm-load-binary-fallback.R:8:3 + 2. └─Pmetrics (local) initialize(...) + +Warning ('test-pm-load-binary-fallback.R:35:1'): (code run outside of `test_that()`) +The `code` argument to `test_that()` must be a braced expression to get accurate file-line information for failures. +Backtrace: + ▆ + 1. └─test_that(...) at test-pm-load-binary-fallback.R:35:1 + 2. └─testthat::test_that(desc, code) at tests/testthat/helper-models.R:4:3 + +Error ('test-pm-load-binary-fallback.R:40:3'): PM_sim$new works from PM_load without compiled artifacts +Error in `reserved_name_conflicts(self$arg_list)`: could not find function "reserved_name_conflicts" +Backtrace: + ▆ + 1. └─PM_model$new(x = modEx, compile = FALSE) at test-pm-load-binary-fallback.R:40:3 + 2. └─Pmetrics (local) initialize(...) +──────────────────────────────────────────────────────────────────────────────── +Maximum number of failures exceeded; quitting at end of file. +ℹ Increase this number with (e.g.) `testthat::set_max_fails(Inf)` + +══ Results ═════════════════════════════════════════════════════════════════════ +── Skipped tests (2) ─────────────────────────────────────────────────────────── +• On CRAN (2): 'test-analytical-indexing.R:1:1', 'test-data-models.R:41:3' + +── Failed tests ──────────────────────────────────────────────────────────────── +Error ('test-data-models.R:18:3'): Model object creation +Error in `reserved_name_conflicts(self$arg_list)`: could not find function "reserved_name_conflicts" +Backtrace: + ▆ + 1. └─build_example_ode_model(compile = FALSE) at test-data-models.R:18:3 + 2. └─PM_model$new(...) at tests/testthat/helper-models.R:57:3 + 3. └─Pmetrics (local) initialize(...) + +Error ('test-data-models.R:30:3'): Model can be reconstructed from an existing PM_model +Error in `reserved_name_conflicts(self$arg_list)`: could not find function "reserved_name_conflicts" +Backtrace: + ▆ + 1. └─build_example_ode_model(compile = FALSE) at test-data-models.R:30:3 + 2. └─PM_model$new(...) at tests/testthat/helper-models.R:57:3 + 3. └─Pmetrics (local) initialize(...) + +Error ('test-data-models.R:69:3'): Analytical models allow multi-line secondary conditionals +Error in `reserved_name_conflicts(self$arg_list)`: could not find function "reserved_name_conflicts" +Backtrace: + ▆ + 1. └─PM_model$new(...) at test-data-models.R:69:3 + 2. └─Pmetrics (local) initialize(...) + +Error ('test-pm-fit-prior-parameters.R:68:3'): PM_model$fit accepts prior file with matching parameter names +Error in `reserved_name_conflicts(self$arg_list)`: could not find function "reserved_name_conflicts" +Backtrace: + ▆ + 1. └─run_fit_with_mocked_engine(prior_df) at test-pm-fit-prior-parameters.R:68:3 + 2. └─PM_model$new(x = NPex$model, compile = FALSE) at test-pm-fit-prior-parameters.R:17:3 + 3. └─Pmetrics (local) initialize(...) + +Error ('test-pm-fit-prior-parameters.R:87:3'): PM_model$fit reorders prior parameter columns to match current PRI order +Error in `reserved_name_conflicts(self$arg_list)`: could not find function "reserved_name_conflicts" +Backtrace: + ▆ + 1. └─run_fit_with_mocked_engine(prior_df) at test-pm-fit-prior-parameters.R:87:3 + 2. └─PM_model$new(x = NPex$model, compile = FALSE) at test-pm-fit-prior-parameters.R:17:3 + 3. └─Pmetrics (local) initialize(...) + +Error ('test-pm-fit-prior-parameters.R:101:3'): PM_model$fit substitutes renamed prior parameter columns when only names changed +Error in `reserved_name_conflicts(self$arg_list)`: could not find function "reserved_name_conflicts" +Backtrace: + ▆ + 1. └─run_fit_with_mocked_engine(prior_df) at test-pm-fit-prior-parameters.R:101:3 + 2. └─PM_model$new(x = NPex$model, compile = FALSE) at test-pm-fit-prior-parameters.R:17:3 + 3. └─Pmetrics (local) initialize(...) + +Error ('test-pm-fit-prior-parameters.R:115:3'): PM_model$fit rejects prior when mismatch is not a pure substitution +Error in `reserved_name_conflicts(self$arg_list)`: could not find function "reserved_name_conflicts" +Backtrace: + ▆ + 1. └─run_fit_with_mocked_engine(prior_df) at test-pm-fit-prior-parameters.R:115:3 + 2. └─PM_model$new(x = NPex$model, compile = FALSE) at test-pm-fit-prior-parameters.R:17:3 + 3. └─Pmetrics (local) initialize(...) + +Error ('test-pm-fit-prior-parameters.R:133:3'): PM_model$fit supports numeric prior run and normalizes theta.csv columns +Error in `reserved_name_conflicts(self$arg_list)`: could not find function "reserved_name_conflicts" +Backtrace: + ▆ + 1. └─PM_model$new(x = NPex$model, compile = FALSE) at test-pm-fit-prior-parameters.R:133:3 + 2. └─Pmetrics (local) initialize(...) + +Error ('test-pm-load-binary-fallback.R:8:3'): PM_load uses saved DSL inputs instead of compiled artifacts +Error in `reserved_name_conflicts(self$arg_list)`: could not find function "reserved_name_conflicts" +Backtrace: + ▆ + 1. └─PM_model$new(x = modEx, compile = FALSE) at test-pm-load-binary-fallback.R:8:3 + 2. └─Pmetrics (local) initialize(...) + +Error ('test-pm-load-binary-fallback.R:40:3'): PM_sim$new works from PM_load without compiled artifacts +Error in `reserved_name_conflicts(self$arg_list)`: could not find function "reserved_name_conflicts" +Backtrace: + ▆ + 1. └─PM_model$new(x = modEx, compile = FALSE) at test-pm-load-binary-fallback.R:40:3 + 2. └─Pmetrics (local) initialize(...) + +[ FAIL 10 | WARN 13 | SKIP 2 | PASS 6 ] +══ Terminated early ════════════════════════════════════════════════════════════ +Error: Test failures +Execution halted diff --git a/tests/testthat/Rplots.pdf b/tests/testthat/Rplots.pdf index e70b3fb29864662fd9886e233a76e2832b6f441e..40ba8557b062c0d9871e3ec68fc4bd1efa5e0a5f 100644 GIT binary patch delta 1572 zcmZXQdpr{e0LLrcXkp}WSwg~Db$i+ivrZ&EQ0$~JYS!90jJ0dhjBpjPoE4uah3fKV zQeJr!aTyNPklUIvN#x|F$gh9vw6kk(E}y5rM+svYprObj)(gkh_+ik07Rs%hrL zR1yzm*td{?b2n7)sQZD8czilG8be}HdpexHDCewH=U|fUJ4mB>3c|3J=v#gBj~72> zSW3xN2Il+-72pPVwEtYq`|{GpqG-O6lw=}5Cfosbl%m{;J=7qW-_EzfnIAKM=&&>M@`%qUz~1wfa^swmwPI-`a~g&iV0L7?--+B! zkqrfaWG6^48HLnmTFB;3i$2DTY}2sX8Ipp+*@QA}HzJe$8ud=vDSw2qaOZ8wsh*<| zmbG#HE zFITnInu$xL;^lNR-?MHlc83Kicu}^T#L>_4t9h=IIX~Qkbix>lq;2yV`l4HNK&oWr&Iw#mI6W z0WQ-sma6AY^TB8j%hc_ch~y19Htje%Nj9y>^Fq$#}mGO zOms1hPYsyzz6GdoOUzA-iiq!OxJfb&`)Su!xWg;|0c~l{1(kYFaT+MJPFBjhBip$P z+3$IMuUP@-Y$9*9^OqDKGYF^I*ZsDySOOUuTyuxyCEwk5Gtb~FjZ<8)Yt2-OVugOA zTF1WiZE-m8NzdDLIXyG!f?TQ<P;crZ21hgDwuu zo%xcwouwL$W3Y4Ifp8t7Q08Z{tCUj55e{;^LCmXpJc?9+CA0-~Iu8(C;>t3$!xqhZ zHU2NXlWF>GO>GM_{fFFqSUy0ParC749L?NjB0TLuIpQ9Q56H*0TuC099}>QFUIIat z+^dr%n)hrXix*67d%uh7bxnJf=3+ggoUm5X&_+t@CK+gFOJ}b0_=A3>xE4wc`!Yayp&h)9!r4I|8r`jx-&eg%uf)?oSq48Dm&A@V;$lv7PYP^*=BGr| z_NJ5;zjLgrz&|s_ER7}ob%0pUsEC|oAv-(9r*E20%`8e)}sq! zzD#|4Pf($}z#p3^ygc09kWj+ZYX$S1_J&1(6IcUAL6+?m1M8a)+x6FXuR}$Bhd34( zl*f2g@FSGUx_0oCf2kkSsA*amg3T9rCwDgbFr2EGi{962WrrgNNmI2Svj zC(jmJY`th3<#$A}`g%4v?5Al`Q28Y;pu)R7hQh8?9D7Y7CCAqM`hAMeb-{(uH5ed0 z;lwysz>yu2dI3p`)_tq5T1pi32VYW`Bu#E!Vt6Bcr8vG*!MFS#`pBon@6N1Bm=2on z6JZ^%3;8hOh39m;_rvMiSlW-JZ?N*f3FVYvRn1=6H20$0OE{{NZ$QMNkT&qYXA@-u z{0HC&90L76hezT5@34sMI%C`(CkzJj-{^!#qWf& zCWe!(8I|j&dS}ZR@*IEvPsA#ZFI4B``D~|Xjx877Iq@sDPheNP>zgSzrSA8s{*x8X zmsA3xUL>CRv%&qJ_rGZ80tS}pAMV!7l3(z|P-R1g?gRri#{AfYuZ@2&g`d|g(_C<3 zOU3tRpMTrlh@Z9T$RP{8@Agto(;v25+Awi1KX#}w(6KFRPd$H|?R0TZ!Qckol5?D~ zEv5%HwcGEkRQYrE!;N&c8?9aCq7%exLr=Tf2N{4PmIi?SCW~3o^&}{ zByag%VHnl-itq6ao#z*fr))SGb~?2r^HFK??z4eX!4DT4s(E}RvZ+X;N}4@>rqlBr zZSOTc9x;)^Svt9Px6iFR+vcD4s5g2MSZ4Gh zw9n+p;yH&WtlwU|Z>iiVCFi|U=E}cX`6hE!c~;Ez@1G?tk0tKiRJGBlTrKNkaq7Vt zxBm(6KK91CxI2G!oavNp3nuv&PbvBtck}o;N3G&lcV5q2{&P{hrO*BB@3UXu{rRuv zSCwZ*=937$i!$+%hc@ir^tIXUtM`}Yy85TT|LpwPeRx9s$8?!j!Rw~-eATWibFNkY zKYiNo<-2(#INDfua{~_)yrMFm>v3W`+4uK z(|4Ia9bmrn@5|OIy-f2{OZT;RKYja^Z_mYf`*&FH+hHz{0IeK$=>Px# diff --git a/tests/testthat/helper-models.R b/tests/testthat/helper-models.R index 545a4a077..36e2e010b 100644 --- a/tests/testthat/helper-models.R +++ b/tests/testthat/helper-models.R @@ -4,30 +4,6 @@ test_that <- function(desc, code) { testthat::test_that(desc, code) } -cleanup_exa_tmp <- function() { - exa_tmp <- tryCatch(temporary_path(), error = function(...) NA_character_) - - if ( - is.character(exa_tmp) && - length(exa_tmp) == 1 && - !is.na(exa_tmp) && - nzchar(exa_tmp) && - dir.exists(exa_tmp) - ) { - unlink(exa_tmp, recursive = TRUE, force = TRUE) - } - - invisible(NULL) -} - -local_exa_tmp_cleanup <- function(env = parent.frame()) { - keep_temp_template <- tolower(Sys.getenv("PMETRICS_TEST_KEEP_EXA_TMP", unset = "true")) %in% c("true", "1", "yes") - if (!keep_temp_template) { - withr::defer(cleanup_exa_tmp(), envir = env) - } - invisible(NULL) -} - build_library_model <- function(model_name, mode = c("analytical", "ode")) { mode <- match.arg(mode) diff --git a/tests/testthat/test-analytical-indexing.R b/tests/testthat/test-analytical-indexing.R index c30e3ed73..920944b13 100644 --- a/tests/testthat/test-analytical-indexing.R +++ b/tests/testthat/test-analytical-indexing.R @@ -37,22 +37,16 @@ make_one_comp_iv_fit_data <- function() { ) } -test_that("Analytical generation preserves 1-based indices and padded sizes", { +test_that("Analytical generation preserves 1-based indices", { mod <- build_one_comp_iv_analytical_model(compile = FALSE) - rust_file <- tempfile(fileext = ".rs") - on.exit(unlink(rust_file), add = TRUE) - - mod$.__enclos_env__$private$write_model_to_rust(rust_file) - rust <- paste(readLines(rust_file), collapse = "\n") + dsl <- mod$dsl() testthat::expect_equal(mod$model_list$n_out, 1) - testthat::expect_equal(mod$model_list$n_out_slots, 2) - testthat::expect_match(rust, "equation::Analytical::new") - testthat::expect_match(rust, "\\.with_nstates\\(2\\)") - testthat::expect_match(rust, "\\.with_ndrugs\\(2\\)") - testthat::expect_match(rust, "\\.with_nout\\(2\\)") - testthat::expect_match(rust, "y\\[1\\]") - testthat::expect_false(grepl("y[0]", rust, fixed = TRUE)) + testthat::expect_match(dsl, "kind = analytical") + testthat::expect_match(dsl, "structure = one_compartment") + testthat::expect_match(dsl, "outputs = outeq_1") + testthat::expect_match(dsl, "out\\(outeq_1\\)") + testthat::expect_false(grepl("outeq_0", dsl, fixed = TRUE)) }) test_that("Analytical fit runs one NPAG cycle with y[1] and one error model", { diff --git a/tests/testthat/test-book-examples.R b/tests/testthat/test-book-examples.R index 368a443ab..b465ba55e 100644 --- a/tests/testthat/test-book-examples.R +++ b/tests/testthat/test-book-examples.R @@ -22,12 +22,11 @@ testthat::test_that("Book model snippets adapted: PM_model construction and mapp testthat::expect_s3_class(model_from_obj, "PM_model") testthat::expect_s3_class(model_rebuilt, "PM_model") testthat::expect_true(length(model_from_obj$model_list$parameters) > 0) - testthat::expect_true("eqn" %in% names(model_from_obj$model_list)) + testthat::expect_true(!is.null(model_from_obj$arg_list$eqn)) }) testthat::test_that("Book workflow snippets adapted: fit then PM_load roundtrip", { testthat::skip_if_not(is_cargo_installed(), "Rust toolchain is required for model compilation.") - local_exa_tmp_cleanup() run_root <- withr::local_tempdir(pattern = "book-workflow-") diff --git a/tests/testthat/test-data-models.R b/tests/testthat/test-data-models.R index 7d0826e2e..2b02b079b 100755 --- a/tests/testthat/test-data-models.R +++ b/tests/testthat/test-data-models.R @@ -23,7 +23,7 @@ test_that("Model object creation", { expect_equal(mod1$model_list$n_eqn, 2) expect_equal(mod1$model_list$n_out, 1) expect_equal(mod1$model_list$type, "ODE") - expect_true(is.null(mod1$binary_path)) + expect_match(mod1$dsl(), "kind = ode") }) test_that("Model can be reconstructed from an existing PM_model", { @@ -33,7 +33,7 @@ test_that("Model can be reconstructed from an existing PM_model", { expect_s3_class(mod2, "PM_model") expect_equal(names(mod2$model_list$pri), c("ka", "ke", "v")) expect_equal(mod2$model_list$type, "ODE") - expect_true(is.null(mod2$binary_path)) + expect_match(mod2$dsl(), "kind = ode") }) @@ -43,12 +43,11 @@ test_that("Current workflow: PM_model + PM_data + PM_model$fit", { is_cargo_installed(), message = "Cargo is required to compile and run PM_model$fit tests." ) - local_exa_tmp_cleanup() mod1 <- build_example_ode_model(compile = TRUE) ex_data <- PM_data$new(data = "ex.csv", quiet = TRUE) - expect_true(file.exists(mod1$binary_path)) + expect_no_error(mod1$compile(quiet = TRUE)) run_path <- withr::local_tempdir() ex_res <- mod1$fit( @@ -62,6 +61,7 @@ test_that("Current workflow: PM_model + PM_data + PM_model$fit", { expect_s3_class(ex_res, "PM_result") expect_true(file.exists(file.path(run_path, "1", "outputs", "PMout.Rdata"))) + expect_true(file.exists(file.path(run_path, "1", "inputs", "model.dsl"))) }) test_that("Analytical models allow multi-line secondary conditionals", { diff --git a/tests/testthat/test-fit-cache-option.R b/tests/testthat/test-fit-cache-option.R new file mode 100644 index 000000000..f10df5a8b --- /dev/null +++ b/tests/testthat/test-fit-cache-option.R @@ -0,0 +1,60 @@ +testthat::skip_on_cran() + +testthat::skip_if_not( + is_cargo_installed(), + message = "Cargo is required to run fit cache option tests." +) + +build_cache_fit_model <- function() { + ke <- b <- v <- NULL + + PM_model$new( + pri = list( + ke = ab(0.1, 1.0), + v = ab(5, 20) + ), + eqn = function() { + dx[1] <- -ke * x[1] + b[1] + }, + out = function() { + y[1] <- x[1] / v + }, + err = list(additive(1.25, c(0.1, 0, 0, 0), fixed = TRUE)), + compile = FALSE + ) +} + +make_cache_fit_data <- function() { + PM_data$new( + data.frame( + id = c("1", "1", "1"), + time = c(0, 1, 2), + evid = c(1L, 0L, 0L), + dose = c(100, NA_real_, NA_real_), + dur = c(0, NA_real_, NA_real_), + input = c(1L, NA_integer_, NA_integer_), + out = c(NA_real_, 10, 9), + outeq = c(NA_integer_, 1L, 1L) + ), + quiet = TRUE + ) +} + +testthat::test_that("fit accepts cache = FALSE on the runtime path", { + mod <- build_cache_fit_model() + dat <- make_cache_fit_data() + run_path <- withr::local_tempdir(pattern = "fit-cache-") + + res <- mod$fit( + data = dat, + path = run_path, + cycles = 1, + points = 20, + cache = FALSE, + report = "none", + quiet = TRUE + ) + + testthat::expect_s3_class(res, "PM_result") + testthat::expect_true(file.exists(file.path(run_path, "1", "outputs", "PMout.Rdata"))) +}) diff --git a/tests/testthat/test-fit-log-controls.R b/tests/testthat/test-fit-log-controls.R new file mode 100644 index 000000000..4f9a57eb7 --- /dev/null +++ b/tests/testthat/test-fit-log-controls.R @@ -0,0 +1,135 @@ +testthat::skip_on_cran() + +testthat::skip_if_not( + is_cargo_installed(), + message = "Cargo is required to run fit log control tests." +) + +testthat::skip_if_not_installed("devtools") + +run_log_control_fit <- function( + progress = FALSE, + write_logs = NULL, + stdout_logs = NULL, + log_level = NULL) { + pkg_root <- normalizePath(".") + temp_dir <- withr::local_tempdir(pattern = "fit-log-controls-") + script_path <- file.path(temp_dir, "run-fit-log-controls.R") + run_path <- file.path(temp_dir, "run") + + args <- c( + "data = dat", + "path = run_path", + "run = 1", + "cycles = 1", + "points = 20", + sprintf("progress = %s", if (isTRUE(progress)) "TRUE" else "FALSE"), + 'report = "none"', + "quiet = TRUE" + ) + + if (!is.null(write_logs)) { + args <- c(args, sprintf("write_logs = %s", if (isTRUE(write_logs)) "TRUE" else "FALSE")) + } + + if (!is.null(stdout_logs)) { + args <- c(args, sprintf("stdout_logs = %s", if (isTRUE(stdout_logs)) "TRUE" else "FALSE")) + } + + if (!is.null(log_level)) { + args <- c(args, sprintf('log_level = "%s"', log_level)) + } + + fit_call <- sprintf("invisible(mod$fit(%s))", paste(args, collapse = ", ")) + + writeLines( + c( + 'Sys.setenv(NOT_CRAN = "true")', + sprintf("devtools::load_all(%s, quiet = TRUE)", dQuote(pkg_root)), + sprintf("run_path <- %s", dQuote(run_path)), + "dir.create(run_path, recursive = TRUE, showWarnings = FALSE)", + "mod <- PM_model$new(", + " pri = list(ke = ab(0.1, 1.0), v = ab(5, 20)),", + " eqn = function() { dx[1] <- -ke * x[1] + b[1] },", + " out = function() { y[1] <- x[1] / v },", + " err = list(additive(1.25, c(0.1, 0, 0, 0), fixed = TRUE)),", + " compile = FALSE", + ")", + "dat <- PM_data$new(", + " data.frame(", + ' id = c("1", "1", "1"),', + " time = c(0, 1, 2),", + " evid = c(1L, 0L, 0L),", + " dose = c(100, NA_real_, NA_real_),", + " dur = c(0, NA_real_, NA_real_),", + " input = c(1L, NA_integer_, NA_integer_),", + " out = c(NA_real_, 10, 9),", + " outeq = c(NA_integer_, 1L, 1L)", + " ),", + " quiet = TRUE", + ")", + fit_call + ), + script_path + ) + + output <- system2( + file.path(R.home("bin"), "Rscript"), + script_path, + stdout = TRUE, + stderr = TRUE, + env = c("NOT_CRAN=true") + ) + + log_path <- file.path(run_path, "1", "outputs", "log.txt") + list( + output = output, + log_path = log_path, + log_exists = file.exists(log_path), + log_lines = if (file.exists(log_path)) readLines(log_path, warn = FALSE) else character() + ) +} + +contains_line <- function(lines, text) { + any(grepl(text, lines, fixed = TRUE)) +} + +testthat::test_that("fit does not write log.txt by default", { + result <- run_log_control_fit() + + testthat::expect_false(result$log_exists) +}) + +testthat::test_that("fit writes log.txt when write_logs = TRUE", { + result <- run_log_control_fit(write_logs = TRUE) + + testthat::expect_true(result$log_exists) + testthat::expect_true(contains_line(result$log_lines, "Objective function =")) +}) + +testthat::test_that("fit suppresses tracing console logs when stdout_logs = FALSE", { + result <- run_log_control_fit(stdout_logs = FALSE) + + testthat::expect_false(contains_line(result$output, "Objective function =")) + testthat::expect_false(contains_line(result$output, "Maximum number of cycles reached")) +}) + +testthat::test_that("fit keeps tracing console logs enabled by default", { + result <- run_log_control_fit() + + testthat::expect_true(contains_line(result$output, "Objective function =")) +}) + +testthat::test_that('fit respects log_level = "WARN" on the console path', { + result <- run_log_control_fit(log_level = "WARN") + + testthat::expect_false(contains_line(result$output, "Objective function =")) + testthat::expect_true(contains_line(result$output, "Maximum number of cycles reached")) +}) + +testthat::test_that("fit keeps INFO console logs enabled by default log_level", { + result <- run_log_control_fit() + + testthat::expect_true(contains_line(result$output, "Objective function =")) + testthat::expect_true(contains_line(result$output, "Maximum number of cycles reached")) +}) diff --git a/tests/testthat/test-fit-progress-option.R b/tests/testthat/test-fit-progress-option.R new file mode 100644 index 000000000..ea3bad341 --- /dev/null +++ b/tests/testthat/test-fit-progress-option.R @@ -0,0 +1,79 @@ +testthat::skip_on_cran() + +testthat::skip_if_not( + is_cargo_installed(), + message = "Cargo is required to run fit progress option tests." +) + +testthat::skip_if_not_installed("devtools") + +run_progress_fit <- function(progress = NULL) { + pkg_root <- normalizePath(".") + temp_dir <- withr::local_tempdir(pattern = "fit-progress-script-") + script_path <- file.path(temp_dir, "run-progress-fit.R") + run_path <- file.path(temp_dir, "run") + + fit_call <- if (is.null(progress)) { + "invisible(mod$fit(data = dat, path = run_path, cycles = 1, points = 20, report = \"none\", quiet = TRUE))" + } else { + sprintf( + "invisible(mod$fit(data = dat, path = run_path, cycles = 1, points = 20, progress = %s, report = \"none\", quiet = TRUE))", + if (isTRUE(progress)) "TRUE" else "FALSE" + ) + } + + writeLines( + c( + 'Sys.setenv(NOT_CRAN = "true")', + sprintf("devtools::load_all(%s, quiet = TRUE)", dQuote(pkg_root)), + sprintf("run_path <- %s", dQuote(run_path)), + "dir.create(run_path, recursive = TRUE, showWarnings = FALSE)", + "mod <- PM_model$new(", + " pri = list(ke = ab(0.1, 1.0), v = ab(5, 20)),", + " eqn = function() { dx[1] <- -ke * x[1] + b[1] },", + " out = function() { y[1] <- x[1] / v },", + " err = list(additive(1.25, c(0.1, 0, 0, 0), fixed = TRUE)),", + " compile = FALSE", + ")", + "dat <- PM_data$new(", + " data.frame(", + ' id = c("1", "1", "1"),', + " time = c(0, 1, 2),", + " evid = c(1L, 0L, 0L),", + " dose = c(100, NA_real_, NA_real_),", + " dur = c(0, NA_real_, NA_real_),", + " input = c(1L, NA_integer_, NA_integer_),", + " out = c(NA_real_, 10, 9),", + " outeq = c(NA_integer_, 1L, 1L)", + " ),", + " quiet = TRUE", + ")", + fit_call + ), + script_path + ) + + system2( + file.path(R.home("bin"), "Rscript"), + script_path, + stdout = TRUE, + stderr = TRUE, + env = c("NOT_CRAN=true") + ) +} + +has_matrix_progress <- function(output) { + any(grepl("Computing log-likelihood matrix:", output, fixed = TRUE)) +} + +testthat::test_that("fit suppresses likelihood matrix progress when progress = FALSE", { + output <- run_progress_fit(FALSE) + + testthat::expect_false(has_matrix_progress(output)) +}) + +testthat::test_that("fit keeps likelihood matrix progress enabled by default", { + output <- run_progress_fit() + + testthat::expect_true(has_matrix_progress(output)) +}) diff --git a/tests/testthat/test-fixed-error-models.R b/tests/testthat/test-fixed-error-models.R new file mode 100644 index 000000000..dce83f79b --- /dev/null +++ b/tests/testthat/test-fixed-error-models.R @@ -0,0 +1,131 @@ +testthat::skip_on_cran() + +testthat::skip_if_not( + is_cargo_installed(), + message = "Cargo is required to run fixed error model fit tests." +) + +build_fixed_error_fit_model <- function(err_model) { + PM_model$new( + pri = list( + ke = ab(0.1, 1.0), + v = ab(5, 20) + ), + eqn = function() { + dx[1] <- -ke * x[1] + b[1] + }, + out = function() { + y[1] <- x[1] / v + }, + err = list(err_model), + compile = FALSE + ) +} + +make_fixed_error_fit_data <- function() { + PM_data$new( + data.frame( + id = c("1", "1", "1"), + time = c(0, 1, 2), + evid = c(1L, 0L, 0L), + dose = c(100, NA_real_, NA_real_), + dur = c(0, NA_real_, NA_real_), + input = c(1L, NA_integer_, NA_integer_), + out = c(NA_real_, 10, 9), + outeq = c(NA_integer_, 1L, 1L) + ), + quiet = TRUE + ) +} + +write_fixed_error_model_file <- function(path, gamlam_line) { + writeLines( + c( + "#pri", + "Ke, 0.1, 1.0", + "V, 5, 20", + "", + "#cov", + "WT", + "", + "#eqn", + "dX[1] = B[1] - Ke * X[1]", + "", + "#out", + "Y[1] = X[1] / V", + "", + "#err", + gamlam_line, + "0.1, 0, 0, 0" + ), + path + ) + + invisible(path) +} + +test_that("fixed additive error model stays fixed on the fit path", { + mod <- build_fixed_error_fit_model(additive(1.25, c(0.1, 0, 0, 0), fixed = TRUE)) + dat <- make_fixed_error_fit_data() + run_path <- withr::local_tempdir(pattern = "fixed-additive-") + + res <- mod$fit( + data = dat, + path = run_path, + cycles = 1, + points = 20, + report = "none", + quiet = TRUE + ) + + gamlam <- res$cycle$gamlam + + testthat::expect_s3_class(res, "PM_result") + testthat::expect_true(isTRUE(mod$model_list$err[[1]]$fixed)) + testthat::expect_equal(mod$model_list$err[[1]]$initial, 1.25) + testthat::expect_equal(nrow(gamlam), 0) + testthat::expect_true(file.exists(file.path(run_path, "1", "outputs", "PMout.Rdata"))) +}) + +test_that("fixed proportional error model stays fixed on the fit path", { + mod <- build_fixed_error_fit_model(proportional(5, c(0.1, 0, 0, 0), fixed = TRUE)) + dat <- make_fixed_error_fit_data() + run_path <- withr::local_tempdir(pattern = "fixed-proportional-") + + res <- mod$fit( + data = dat, + path = run_path, + cycles = 1, + points = 20, + report = "none", + quiet = TRUE + ) + + gamlam <- res$cycle$gamlam + + testthat::expect_s3_class(res, "PM_result") + testthat::expect_true(isTRUE(mod$model_list$err[[1]]$fixed)) + testthat::expect_equal(mod$model_list$err[[1]]$initial, 5) + testthat::expect_equal(nrow(gamlam), 0) + testthat::expect_true(file.exists(file.path(run_path, "1", "outputs", "PMout.Rdata"))) +}) + +test_that("PM_model file import preserves fixed gamma or lambda markers", { + tmp_dir <- withr::local_tempdir(pattern = "fixed-error-file-") + gamma_path <- file.path(tmp_dir, "gamma-fixed.txt") + lambda_path <- file.path(tmp_dir, "lambda-fixed.txt") + + write_fixed_error_model_file(gamma_path, "G=5!") + write_fixed_error_model_file(lambda_path, "L=1.5!") + + gamma_model <- PM_model$new(x = gamma_path, compile = FALSE) + lambda_model <- PM_model$new(x = lambda_path, compile = FALSE) + + testthat::expect_true(isTRUE(gamma_model$model_list$err[[1]]$fixed)) + testthat::expect_identical(gamma_model$model_list$err[[1]]$type, "proportional") + testthat::expect_equal(gamma_model$model_list$err[[1]]$initial, 5) + + testthat::expect_true(isTRUE(lambda_model$model_list$err[[1]]$fixed)) + testthat::expect_identical(lambda_model$model_list$err[[1]]$type, "additive") + testthat::expect_equal(lambda_model$model_list$err[[1]]$initial, 1.5) +}) diff --git a/tests/testthat/test-pm-fit-prior-parameters.R b/tests/testthat/test-pm-fit-prior-parameters.R index dd6c7e7a5..d256fb333 100644 --- a/tests/testthat/test-pm-fit-prior-parameters.R +++ b/tests/testthat/test-pm-fit-prior-parameters.R @@ -15,19 +15,19 @@ make_prior_df <- function(param_names, n = 8, seed = 42) { run_fit_with_mocked_engine <- function(prior_input) { model <- PM_model$new(x = NPex$model, compile = FALSE) - model$binary_path <- tempfile(fileext = ".pmx") - file.create(model$binary_path) run_root <- withr::local_tempdir(pattern = "prior-params-") prior_file <- file.path(run_root, "input-prior.csv") utils::write.csv(prior_input, prior_file, row.names = FALSE) prior_arg <- prior_file + captured_model_source <- NULL captured_prior <- NULL captured_wd <- NULL fit_result <- testthat::with_mocked_bindings( - fit = function(model_path, data, params, output_path, kind) { + fit = function(model_source, data, params, output_path, kind, solver = NULL) { + captured_model_source <<- model_source captured_prior <<- params$prior captured_wd <<- getwd() dir.create(output_path, recursive = TRUE) @@ -51,6 +51,7 @@ run_fit_with_mocked_engine <- function(prior_input) { list( fit_result = fit_result, + captured_model_source = captured_model_source, captured_prior = captured_prior, captured_wd = captured_wd, prior_csv = file.path(captured_wd, "prior.csv"), @@ -67,6 +68,8 @@ test_that("PM_model$fit accepts prior file with matching parameter names", { out <- run_fit_with_mocked_engine(prior_df) expect_s3_class(out$fit_result, "PM_result") + expect_true(is.character(out$captured_model_source)) + expect_gt(nchar(out$captured_model_source), 0) expect_equal(out$captured_prior, "prior.csv") expect_false(identical(out$captured_wd, getwd())) expect_true(file.exists(out$prior_csv)) @@ -128,14 +131,12 @@ test_that("PM_model$fit supports numeric prior run and normalizes theta.csv colu utils::write.csv(theta_df, file.path(theta_dir, "theta.csv"), row.names = FALSE) model <- PM_model$new(x = NPex$model, compile = FALSE) - model$binary_path <- tempfile(fileext = ".pmx") - file.create(model$binary_path) captured_prior <- NULL captured_wd <- NULL fit_result <- testthat::with_mocked_bindings( - fit = function(model_path, data, params, output_path, kind) { + fit = function(model_source, data, params, output_path, kind, solver = NULL) { captured_prior <<- params$prior captured_wd <<- getwd() dir.create(output_path, recursive = TRUE) diff --git a/tests/testthat/test-pm-load-binary-fallback.R b/tests/testthat/test-pm-load-binary-fallback.R deleted file mode 100644 index 91a4b624a..000000000 --- a/tests/testthat/test-pm-load-binary-fallback.R +++ /dev/null @@ -1,92 +0,0 @@ -library(Pmetrics) - -test_that("PM_load tolerates missing inputs binary and fit recompiles", { - local_exa_tmp_cleanup() - run_root <- tempfile("pmetrics-load-fallback-") - dir.create(run_root, recursive = TRUE) - - model_local <- PM_model$new(x = modEx, compile = FALSE) - run_initial <- suppressMessages( - model_local$fit( - data = dataEx, - path = run_root, - run = 1, - overwrite = TRUE, - report = "none", - quiet = TRUE - ) - ) - - expect_true(inherits(run_initial, "PM_result")) - - inputs_dir <- file.path(run_root, "1", "inputs") - pmx_files <- list.files(inputs_dir, pattern = "\\.pmx$", full.names = TRUE) - expect_gt(length(pmx_files), 0) - - backup_paths <- paste0(pmx_files, ".bak") - file.rename(pmx_files, backup_paths) - - loaded_missing <- PM_load(path = run_root, run = 1) - expect_true(inherits(loaded_missing, "PM_result")) - expect_true(is.null(loaded_missing$model$binary_path)) - - run_recompiled <- suppressMessages( - loaded_missing$model$fit( - data = loaded_missing$data, - path = run_root, - run = 2, - overwrite = TRUE, - report = "none", - quiet = TRUE - ) - ) - - expect_true(is.character(run_recompiled$model$binary_path)) - expect_true(file.exists(run_recompiled$model$binary_path)) -}) - -test_that("PM_sim$new recompiles when PM_load has no inputs binary", { - local_exa_tmp_cleanup() - run_root <- tempfile("pmetrics-sim-fallback-") - dir.create(run_root, recursive = TRUE) - - model_local <- PM_model$new(x = modEx, compile = FALSE) - run_initial <- suppressMessages( - model_local$fit( - data = dataEx, - path = run_root, - run = 1, - overwrite = TRUE, - report = "none", - quiet = TRUE - ) - ) - - expect_true(inherits(run_initial, "PM_result")) - - inputs_dir <- file.path(run_root, "1", "inputs") - pmx_files <- list.files(inputs_dir, pattern = "\\.pmx$", full.names = TRUE) - expect_gt(length(pmx_files), 0) - - file.rename(pmx_files, paste0(pmx_files, ".bak")) - - loaded_missing <- PM_load(path = run_root, run = 1) - expect_true(is.null(loaded_missing$model$binary_path)) - - sim_from_missing <- suppressMessages( - PM_sim$new( - poppar = loaded_missing$final, - model = loaded_missing$model, - data = loaded_missing$data, - include = 1, - nsim = 1, - predInt = 1 - ) - ) - - expect_true(inherits(sim_from_missing, "PM_sim")) - # PM_model is R6 (reference semantics), so compile() inside PM_sim$new updates - # loaded_missing$model$binary_path in place - expect_true(is.character(loaded_missing$model$binary_path)) - expect_true(file.exists(loaded_missing$model$binary_path)) -}) diff --git a/tests/testthat/test-pm-load-dsl-roundtrip.R b/tests/testthat/test-pm-load-dsl-roundtrip.R new file mode 100644 index 000000000..db949a0bb --- /dev/null +++ b/tests/testthat/test-pm-load-dsl-roundtrip.R @@ -0,0 +1,72 @@ +library(Pmetrics) + +test_that("PM_load reopens runs from saved DSL input", { + run_root <- tempfile("pmetrics-load-dsl-") + dir.create(run_root, recursive = TRUE) + + model_local <- PM_model$new(x = modEx, compile = FALSE) + run_initial <- suppressMessages( + model_local$fit( + data = dataEx, + path = run_root, + run = 1, + overwrite = TRUE, + report = "none", + quiet = TRUE + ) + ) + + expect_true(inherits(run_initial, "PM_result")) + + inputs_dir <- file.path(run_root, "1", "inputs") + dsl_path <- file.path(inputs_dir, "model.dsl") + expect_true(file.exists(dsl_path)) + + expect_no_warning(loaded <- PM_load(path = run_root, run = 1)) + expect_true(inherits(loaded, "PM_result")) + expect_s3_class(loaded$op, "PM_op") + expect_s3_class(loaded$pop, "PM_pop") + expect_s3_class(loaded$post, "PM_post") + expect_s3_class(loaded$cycle, "PM_cycle") + expect_equal( + trimws(as.character(loaded$model$dsl())), + trimws(paste(readLines(dsl_path, warn = FALSE), collapse = "\n")) + ) +}) + +test_that("PM_sim$new works from PM_load using saved DSL input", { + run_root <- tempfile("pmetrics-sim-dsl-") + dir.create(run_root, recursive = TRUE) + + model_local <- PM_model$new(x = modEx, compile = FALSE) + run_initial <- suppressMessages( + model_local$fit( + data = dataEx, + path = run_root, + run = 1, + overwrite = TRUE, + report = "none", + quiet = TRUE + ) + ) + + expect_true(inherits(run_initial, "PM_result")) + + inputs_dir <- file.path(run_root, "1", "inputs") + expect_true(file.exists(file.path(inputs_dir, "model.dsl"))) + + expect_no_warning(loaded <- PM_load(path = run_root, run = 1)) + + sim_from_missing <- suppressMessages( + PM_sim$new( + poppar = loaded$final, + model = loaded$model, + data = loaded$data, + include = 1, + nsim = 1, + predInt = 1 + ) + ) + + expect_true(inherits(sim_from_missing, "PM_sim")) +}) diff --git a/tests/testthat/test-pm-model-guards.R b/tests/testthat/test-pm-model-guards.R index eea20f407..677f78d0d 100644 --- a/tests/testthat/test-pm-model-guards.R +++ b/tests/testthat/test-pm-model-guards.R @@ -126,7 +126,7 @@ testthat::test_that("PM_model$new flags reserved names and dynamic ODE indices", ) expect_s3_class(reserved, "PM_model") - expect_true(is.null(reserved$binary_path)) + expect_match(reserved$dsl(), "kind = ode") dynamic_index <- PM_model$new( pri = list( @@ -145,7 +145,7 @@ testthat::test_that("PM_model$new flags reserved names and dynamic ODE indices", ) expect_s3_class(dynamic_index, "PM_model") - expect_true(is.null(dynamic_index$binary_path)) + expect_error(dynamic_index$dsl(), "Dynamic indices are not supported") }) testthat::test_that("PM_model$map enforces required arguments", { diff --git a/tests/testthat/test-pmcore-pharmsol-migration.R b/tests/testthat/test-pmcore-pharmsol-migration.R index b91c064f0..9b8932516 100644 --- a/tests/testthat/test-pmcore-pharmsol-migration.R +++ b/tests/testthat/test-pmcore-pharmsol-migration.R @@ -25,53 +25,91 @@ build_passthrough_ode_model <- function(solver = NULL) { )) } -test_that("ODE generation uses ode! and preserves 1-based indices", { +build_shared_input_ode_model <- function() { + PM_model$new(list( + pri = list( + ke = ab(0.1, 1.5), + v = ab(5, 15) + ), + eqn = function() { + dx[1] <- -(ke) * x[1] + rateiv[1] + b[1] + }, + out = function() { + y[1] <- x[1] / v + }, + err = list(additive(1, c(0.1, 0, 0, 0))) + )) +} + +test_that("ODE DSL generation preserves 1-based indices", { mod <- build_passthrough_ode_model("TSIT45") - rust_file <- tempfile(fileext = ".rs") - on.exit(unlink(rust_file), add = TRUE) - - mod$.__enclos_env__$private$write_model_to_rust(rust_file) - rust <- paste(readLines(rust_file), collapse = "\n") - - testthat::expect_match(rust, "fn build_eqn\\(\\) -> impl Equation") - testthat::expect_match(rust, "ode! \\{") - testthat::expect_match(rust, "dx\\[1\\]") - testthat::expect_match(rust, "rateiv\\[1\\]") - testthat::expect_match(rust, "y\\[1\\]") - testthat::expect_match( - rust, - "\\.with_solver\\(OdeSolver::ExplicitRk\\(ExplicitRkTableau::Tsit45\\)\\)" - ) - testthat::expect_false(grepl("dx[0]", rust, fixed = TRUE)) + dsl <- mod$dsl() + + testthat::expect_match(dsl, "kind = ode") + testthat::expect_match(dsl, "dx\\(state_1\\)") + testthat::expect_match(dsl, "infusion\\(input_1\\) -> state_1") + testthat::expect_match(dsl, "out\\(outeq_1\\) = state_1 / v ~ continuous\\(\\)") + testthat::expect_false(grepl("state_0", dsl, fixed = TRUE)) + testthat::expect_no_error(mod$compile(quiet = TRUE)) +}) + +test_that("PM_model$dsl prints readable DSL and removes explicit route input terms", { + mod <- build_shared_input_ode_model() + + dsl <- mod$dsl() + printed <- capture.output(print(dsl)) + + testthat::expect_match(dsl, "bolus\\(input_1_bolus\\) -> state_1") + testthat::expect_match(dsl, "infusion\\(input_1_infusion\\) -> state_1") + testthat::expect_match(dsl, "dx\\(state_1\\) = -ke \\* state_1") + testthat::expect_match(dsl, "out\\(outeq_1\\) = state_1 / v ~ continuous\\(\\)") + testthat::expect_false(grepl("+ 0", dsl, fixed = TRUE)) + testthat::expect_false(any(grepl("[1]", printed, fixed = TRUE))) + testthat::expect_identical(printed[[1]], "name = user") + testthat::expect_identical(printed[[2]], "kind = ode") }) test_that("Analytical migration compiles and reports analytical parameters", { mod <- build_library_model("one_comp_iv", mode = "analytical") - rust_file <- tempfile(fileext = ".rs") - on.exit(unlink(rust_file), add = TRUE) + dsl <- mod$dsl() - mod$.__enclos_env__$private$write_model_to_rust(rust_file) - rust <- paste(readLines(rust_file), collapse = "\n") - - testthat::expect_match(rust, "equation::Analytical::new") - testthat::expect_match(rust, "one_compartment") - testthat::expect_match(rust, "\\.with_nstates\\(") - testthat::expect_match(rust, "\\.with_ndrugs\\(") - testthat::expect_match(rust, "\\.with_nout\\(") + testthat::expect_match(dsl, "kind = analytical") + testthat::expect_match(dsl, "structure = one_compartment") + testthat::expect_match(dsl, "outputs = outeq_1") + testthat::expect_match(dsl, "out\\(outeq_1\\)") testthat::expect_no_error(mod$compile(quiet = TRUE)) - testthat::expect_true(file.exists(mod$binary_path)) testthat::expect_equal( - model_parameters(mod$binary_path, "analytical"), + model_parameters(mod$dsl(), "analytical"), tolower(names(mod$model_list$pri)) ) }) -test_that("template compilation path stays outside package workspace during checks", { - withr::local_envvar(c(`_R_CHECK_PACKAGE_NAME_` = "Pmetrics", env = NA_character_)) +test_that("PM_model$debug_dsl reports DSL text and runtime validation state", { + mod <- build_passthrough_ode_model("TSIT45") - testthat::expect_equal( - Pmetrics:::resolve_template_path(), - file.path(temporary_path(), "template") - ) + debug <- mod$debug_dsl(quiet = TRUE) + + testthat::expect_true(debug$ok) + testthat::expect_identical(debug$stage, "validated") + testthat::expect_equal(trimws(debug$dsl), trimws(as.character(mod$dsl()))) + testthat::expect_true(grepl("^\\s*1 \\| ", debug$numbered_dsl)) + testthat::expect_null(debug$diagnostic) +}) + +test_that("PM_model$debug_dsl captures runtime validation diagnostics", { + mod <- build_passthrough_ode_model("TSIT45") + mod$arg_list$solver <- "bad" + mod$model_list$solver <- "bad" + + debug <- mod$debug_dsl(quiet = TRUE) + + testthat::expect_false(debug$ok) + testthat::expect_identical(debug$stage, "runtime") + testthat::expect_match(debug$diagnostic, "Unsupported ODE solver: BAD") + testthat::expect_true(grepl("kind = ode", debug$dsl, fixed = TRUE)) +}) + +test_that("PM_build is a no-op under the DSL runtime", { + testthat::expect_null(PM_build()) }) From 30a25e8bf0b095b0963f3080c687b3fa8301ef45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20D=2E=20Ot=C3=A1lvaro?= Date: Mon, 25 May 2026 11:44:04 +0100 Subject: [PATCH 2/7] pin Pmcore --- Cargo.lock | 1 + inst/Examples/Rscript/bimodal_ke.R | 1 - src/rust/Cargo.toml | 2 +- src/rust/src/settings.rs | 56 ++++++++++++++++++++++++-- tests/testthat/test-fit-cache-option.R | 18 +++++++++ 5 files changed, 73 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6eaeca44c..c0f0ece13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1677,6 +1677,7 @@ dependencies = [ [[package]] name = "pmcore" version = "0.25.2" +source = "git+https://github.com/LAPKB/PMcore?rev=668dd305436c75d4fdef29138261540c114c0c34#668dd305436c75d4fdef29138261540c114c0c34" dependencies = [ "anyhow", "argmin", diff --git a/inst/Examples/Rscript/bimodal_ke.R b/inst/Examples/Rscript/bimodal_ke.R index 8e3f38988..a1c1ae98d 100644 --- a/inst/Examples/Rscript/bimodal_ke.R +++ b/inst/Examples/Rscript/bimodal_ke.R @@ -38,7 +38,6 @@ fit <- model$fit( points = 2028, seed = 22, idelta = 0.01, - report = "none", overwrite = TRUE ) diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 4ba54d883..7d5d15a82 100755 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -9,7 +9,7 @@ name = 'pm_rs' [dependencies] extendr-api = "=0.9.0" -pmcore = { version = "=0.25.2", path = "../../../PMcore-structure", features = ["dsl-jit"] } +pmcore = { version = "=0.25.2", git = "https://github.com/LAPKB/PMcore", rev = "668dd305436c75d4fdef29138261540c114c0c34", features = ["dsl-jit"] } rayon = "1.10.0" anyhow = "1.0.97" diff --git a/src/rust/src/settings.rs b/src/rust/src/settings.rs index c700bb7ba..98543a82b 100644 --- a/src/rust/src/settings.rs +++ b/src/rust/src/settings.rs @@ -24,9 +24,15 @@ fn get_str(map: &HashMap<&str, Robj>, key: &str) -> AnyResult { fn get_real_or(map: &HashMap<&str, Robj>, key: &str, default: f64) -> AnyResult { match map.get(key) { - Some(value) => value - .as_real() - .ok_or_else(|| anyhow!("Setting '{}' is not numeric", key)), + Some(value) => { + if let Some(real) = value.as_real() { + Ok(real) + } else if let Some(integer) = value.as_integer() { + Ok(integer as f64) + } else { + Err(anyhow!("Setting '{}' is not numeric", key)) + } + } None => Ok(default), } } @@ -431,6 +437,50 @@ mod tests { } } + #[test] + fn settings_accepts_integer_max_cycles() { + test! { + let error_model = List::from_pairs(vec![ + ("initial", r!(2.0)), + ("type", r!(["additive"])), + ("fixed", r!(false)), + ("coeff", r!([0.0, 0.10, 0.0, 0.0])), + ]); + + let settings_list = List::from_pairs(vec![ + ( + "ranges", + list!(ke = r!([0.05, 1.0]), v = r!([5.0, 50.0])).into(), + ), + ("algorithm", r!("NPAG")), + ("error_models", List::from_values(vec![error_model]).into()), + ("max_cycles", r!(2_i32)), + ("prior", r!("sobol")), + ("points", r!(8.0)), + ("seed", r!(7.0)), + ]); + + let builder = settings( + settings_list, + equation(), + data(), + std::env::temp_dir() + .join("pm-rs-settings-integer-cycles") + .to_string_lossy() + .as_ref(), + ) + .expect("integer max_cycles should be accepted"); + + match builder { + FitBuilder::Npag(builder) => { + let compiled = builder.build().expect("builder should build").compile().expect("problem should compile"); + assert_eq!(compiled.runtime_options().cycles, 2); + } + FitBuilder::Npod(_) | FitBuilder::PostProb(_) => panic!("expected NPAG builder"), + } + } + } + #[test] fn settings_defaults_cache_to_true() { test! { diff --git a/tests/testthat/test-fit-cache-option.R b/tests/testthat/test-fit-cache-option.R index f10df5a8b..fbc4e84c7 100644 --- a/tests/testthat/test-fit-cache-option.R +++ b/tests/testthat/test-fit-cache-option.R @@ -58,3 +58,21 @@ testthat::test_that("fit accepts cache = FALSE on the runtime path", { testthat::expect_s3_class(res, "PM_result") testthat::expect_true(file.exists(file.path(run_path, "1", "outputs", "PMout.Rdata"))) }) + +testthat::test_that("fit accepts integer cycles on the runtime path", { + mod <- build_cache_fit_model() + dat <- make_cache_fit_data() + run_path <- withr::local_tempdir(pattern = "fit-cycles-integer-") + + res <- mod$fit( + data = dat, + path = run_path, + cycles = 1L, + points = 20, + report = "none", + quiet = TRUE + ) + + testthat::expect_s3_class(res, "PM_result") + testthat::expect_true(file.exists(file.path(run_path, "1", "outputs", "PMout.Rdata"))) +}) From 7d3a53ad5ed18ae793ba100a4bfc450aadc2cc0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20D=2E=20Ot=C3=A1lvaro?= Date: Wed, 27 May 2026 18:14:28 +0100 Subject: [PATCH 3/7] live report --- ANALYSIS.md | 531 +++++++++++ Cargo.lock | 5 +- Cargo.toml | 3 + DESCRIPTION | 2 +- NAMESPACE | 3 + R/PM_bridge_errors.R | 198 ++++ R/PM_cov.R | 25 +- R/PM_cycle.R | 59 +- R/PM_final.R | 42 +- R/PM_fit_payload.R | 845 ++++++++++++++++++ R/PM_live.R | 150 ++++ R/PM_model.R | 178 +++- R/PM_op.R | 43 +- R/PM_parse.R | 84 +- R/PM_pop.R | 26 +- R/PM_post.R | 26 +- R/PM_report.R | 90 +- R/PM_result.R | 153 +++- R/extendr-wrappers.R | 38 + R/globalVariables.R | 4 +- inst/Examples/Rscript/bimodal_ke.R | 3 +- man/PM_load.Rd | 6 +- man/PM_model.Rd | 9 +- man/PM_report.Rd | 13 +- src/rust/Cargo.toml | 4 + src/rust/src/bridge_errors.rs | 163 ++++ src/rust/src/executor.rs | 319 ++++++- src/rust/src/fit_payload.rs | 821 +++++++++++++++++ src/rust/src/lib.rs | 379 ++++++-- src/rust/src/live.rs | 495 ++++++++++ src/rust/src/settings.rs | 160 +++- tests/testthat/helper-bridge-errors.R | 32 + tests/testthat/test-fit-payload-result.R | 561 ++++++++++++ tests/testthat/test-live-report-bridge.R | 130 +++ tests/testthat/test-live-report-handoff.R | 166 ++++ .../testthat/test-pm-load-snapshot-fallback.R | 193 ++++ tests/testthat/test-pm-report.R | 94 ++ 37 files changed, 5623 insertions(+), 430 deletions(-) create mode 100644 ANALYSIS.md create mode 100644 R/PM_bridge_errors.R create mode 100644 R/PM_fit_payload.R create mode 100644 R/PM_live.R create mode 100644 src/rust/src/bridge_errors.rs create mode 100644 src/rust/src/fit_payload.rs create mode 100644 src/rust/src/live.rs create mode 100644 tests/testthat/helper-bridge-errors.R create mode 100644 tests/testthat/test-fit-payload-result.R create mode 100644 tests/testthat/test-live-report-bridge.R create mode 100644 tests/testthat/test-live-report-handoff.R create mode 100644 tests/testthat/test-pm-load-snapshot-fallback.R create mode 100644 tests/testthat/test-pm-report.R diff --git a/ANALYSIS.md b/ANALYSIS.md new file mode 100644 index 000000000..de2b754d6 --- /dev/null +++ b/ANALYSIS.md @@ -0,0 +1,531 @@ +# Pmetrics DSL Compilation & Model Execution - Comprehensive Analysis + +## Architecture Overview + +Pmetrics 3.0+ is a hybrid R/Rust application where R serves as the user-facing API layer and Rust (via PMcore) handles the heavy computational work. The system follows a clear pipeline: + +``` +R Model Definition → DSL Compilation → Rust JIT → PMcore Estimation → Fit Payload → PM_result → Report +``` + +--- + +## 1. DSL Compilation + +### 1.1 R-Side Model Definition (`R/PM_model.R`) + +The `PM_model` R6 class is the central abstraction. Users define models through named blocks: + +| Block | Type | Purpose | +|-------|------|---------| +| `pri` | list | Primary (random) parameters with `ab()` or `msd()` creators | +| `cov` | list | Covariates with `interp()` creator | +| `sec` | function | Secondary/global equations | +| `eqn` | function | Model equations (ODE or analytical template) | +| `lag` | function | Lag time / delayed absorption | +| `fa` | function | Bioavailability (fraction absorbed) | +| `ini` | function | Initial compartment conditions | +| `out` | function | Output equations (predictions) | +| `err` | list | Error models (`additive()`, `proportional()`) | +| `solver` | string | ODE solver selection (BDF, TRBDF2, ESDIRK34, TSIT45) | + +```r +mod_list <- list( + pri = list(CL = ab(10, 200), V0 = ab(0, 100), ka = ab(0, 3)), + cov = list(wt = interp()), + sec = function() { V <- V0 * (wt / 70) }, + eqn = function() { + dx[1] <- -ka * x[1] + dx[2] <- rateiv[1] + ka * x[1] - (ke + k23) * x[2] + k32 * x[3] + }, + out = function() { y[1] <- x[1] / V }, + err = list(proportional(2, c(0.1, 0.15, 0, 0))) +) +mod <- PM_model$new(mod_list) +``` + +### 1.2 Model Validation (`PM_model$initialize`) + +The `initialize` method performs extensive validation: + +1. **Reserved name conflicts** - Checks against `t, x, dx, p, b, bolus, r, rateiv, cov, y` +2. **Required blocks** - `pri`, `eqn`, `out`, `err` are mandatory +3. **Model type detection** - Analytical (library template) vs ODE (user-defined) +4. **Index literal enforcement** - ODE models require literal compartment indices (e.g., `x[1]` not `x[i]`) so the Rust `ode!` macro can infer dimensions at compile time +5. **Parameter completeness** - For analytical templates, verifies all required parameters are defined across blocks +6. **Error model count** - One error model per output equation + +### 1.3 DSL Parsing Helpers (`R/model_dsl.R`) + +Small but critical utilities for AST analysis: + +- `statement_exprs()` - Extracts statements from function bodies +- `reserved_name_conflicts()` - Detects naming collisions with DSL reserved words +- `get_max_index()` / `collect_index_refs()` - Finds max compartment/input indices from bracket expressions +- `get_assignments()` / `get_assignment_indices()` - Counts `dx[i]` and `y[i]` assignments +- `has_nonliteral_index()` - Validates ODE models use static indices + +### 1.4 Rust-Side Runtime Compilation (`src/rust/src/lib.rs`) + +The `compile_runtime_model` function bridges R to Rust: + +```rust +// Called via extendr from R +pub fn compile_runtime_model( + model_source: &str, + kind: &str, // "ode" or "analytical" + solver: Option +) -> AnyResult +``` + +Key flow: +1. Validates model source is non-empty and kind is supported +2. Calls `dsl::compile_module_source_to_runtime()` with `RuntimeCompilationTarget::Jit` +3. Validates the compiled model's actual kind matches the declared kind +4. Applies ODE solver selection if provided +5. Returns a `CompiledRuntimeModel` (enum: `Ode`, `Analytical`, `Sde`) + +The actual DSL compilation happens in **PMcore** at `PMcore/compile/`: + +| File | Responsibility | +|------|----------------| +| `mod.rs` | Entry point, compilation pipeline orchestration | +| `caches.rs` | Prediction caching infrastructure | +| `compiled_problem.rs` | Builds the computable problem from model + data | +| `design_context.rs` | Context for model design-time analysis | +| `observation_index.rs` | Maps observations to output equations | +| `validation.rs` | Pre-flight validation of model-data compatibility | + +### 1.5 Model Library (`R/mod_lib.R`) + +Pre-defined analytical PK templates (e.g., `one_comp_iv`, `two_comp_iv`). The `mod_list` is a tibble of model definitions. Key functions: + +- `get_found_model()` - Detects which template (if any) is referenced in `eqn` +- `model_lib()` - Launches the PmetricsModelLib Shiny app for browsing templates +- `generate_model_code_text()` - Generates ready-to-use `PM_model$new()` code from a template + +--- + +## 2. Model Execution + +### 2.1 The Fit Pipeline (`R/PM_model.R::fit`) + +The `$fit()` method is the main entry point, replacing the legacy `NPrun()`: + +``` +R fit() → PM_data → PM_model → compile → Rust extendr → PMcore fit → FitPayload → PM_result +``` + +Key steps: + +1. **Data preparation** - Creates/validates `PM_data` object from file or data frame +2. **Path setup** - Creates output directory with run number +3. **Parameter assembly** - Builds fit parameters (ranges, error models, prior, points) +4. **Rust invocation** - Calls into Rust via extendr bindings +5. **Result construction** - Builds `PM_result` from the JSON payload +6. **Snapshot saving** - Saves `PMout.Rdata` for reproducibility + +### 2.2 Rust Execution Layer (`src/rust/src/executor.rs`) + +```rust +pub(crate) fn fit( + equation: E, // Compiled runtime model + data: Data, // Parsed Pmetrics data + params: List, // R list of fit parameters + output_path: &str // Output directory +) -> Result +``` + +The executor: +1. Extracts live session ID, idelta, tad from params +2. Builds a `NonparametricEstimationProblemBuilder` via `settings()` +3. If a live session is active, runs with progress callbacks +4. Otherwise runs in-memory +5. Materializes a `FitPayload` from the `FitResult` + +### 2.3 Settings Builder (`src/rust/src/settings.rs`) + +The `settings()` function translates R parameters into PMcore's `NonparametricEstimationProblemBuilder`: + +```rust +pub(crate) enum FitBuilder { + Npag(NonparametricEstimationProblemBuilder), + Npod(NonparametricEstimationProblemBuilder), + PostProb(NonparametricEstimationProblemBuilder), +} +``` + +Each builder method (`parameter`, `error`, `cycles`, `cache`, `progress`, `prior`, etc.) is dispatched across all three algorithm variants. + +### 2.4 PMcore Estimation Engine (`PMcore/src/estimation/`) + +| Path | Purpose | +|------|---------| +| `nonparametric/` | NPAG - Nonparametric Adaptive Grid algorithm | +| `parametric/` | NPOD - Nonparametric Optimization by Duality | + +The nonparametric workspace (`NonparametricWorkspace`) manages: +- Cycle-by-cycle adaptive grid updates +- Theta matrix (support point values × probabilities) +- Posterior calculations +- Prediction generation + +### 2.5 Fit Payload Serialization (`src/rust/src/fit_payload.rs`) + +The `FitPayload` struct bridges Rust → R: + +```rust +pub struct FitPayload { + parameter_names: Vec, + covariate_names: Vec, + iterations: Vec>, // Cycle-by-cycle metrics + theta: Vec>, // Support point values + posterior: Vec>, // Individual posteriors + predictions: Vec, // Population/individual predictions + covariates: Vec>, // Time-varying covariates +} +``` + +Each field is serialized to JSON, sent to R, then deserialized in `R/PM_fit_payload.R`: + +```r +decode_fit_payload <- function(payload_json, fit_params) { + parsed <- jsonlite::fromJSON(payload_json) + # ... tibble construction with type coercion + structure(list( + parameter_names, covariate_names, iterations, theta, + posterior, predictions, covariates, config + ), class = "PM_fit_payload") +} +``` + +### 2.6 PM_result Construction (`R/PM_fit_payload.R`) + +```r +build_pm_result_from_fit_payload <- function(payload_json, data, model, fit_params, path) { + fit_payload <- decode_fit_payload(payload_json, fit_params) + + out <- list( + pop = PM_pop$new(fit_payload, path), + post = PM_post$new(fit_payload, path), + final = PM_final$new(fit_payload, path), + cycle = PM_cycle$new(fit_payload, path), + op = PM_op$new(fit_payload, path), + cov = PM_cov$new(fit_payload, path), + data = data, + model = model, + success = TRUE + ) + + save_pm_result_snapshot(out, path) + PM_result$new(out, path = path, quiet = TRUE) +} +``` + +Each result component (`PM_pop`, `PM_post`, `PM_final`, `PM_cycle`, `PM_op`, `PM_cov`) is an R6 object that wraps the corresponding tibble from the payload. + +--- + +## 3. Live Execution System + +### 3.1 Architecture + +The live execution system enables real-time progress reporting during model fitting via a TCP-based session protocol. + +``` +R (PM_live.R) ←JSON→ TCP ←JSON→ Rust (live.rs) ←events→ PMcore (FitProgress) + ↑ + Shiny App (PmetricsReports) +``` + +### 3.2 R-Side Live Session (`R/PM_live.R`) + +Key functions: + +| Function | Purpose | +|----------|---------| +| `start_live_report_session()` | Launches the Shiny app and connects to Rust | +| `send_live_report_result()` | Serializes and sends the final result via gzip+base64 | +| `send_live_report_failure()` | Sends failure messages to the live session | +| `encode_live_report_result()` | Gzip-compresses + base64-encodes the serialized result | + +The live session registry is a `OnceLock>>>` shared across all R threads. + +### 3.3 Rust Live Session (`src/rust/src/live.rs`) + +```rust +pub struct LiveSession { + id: String, // Unique session identifier + host: String, // "127.0.0.1" + port: u16, // Dynamic TCP port + state: Mutex, +} + +enum LiveSessionMessage { + SessionStarted { session_id }, + Progress { event: FitProgress }, + FitFailed { message }, + FinalReportReady { result_payload, report_generated_at }, + ReportFailed { message }, + SessionClosed, +} +``` + +Key features: +- **Non-blocking TCP listener** - Accepts client connections without blocking the fit +- **Message backlog** - Up to 1000 messages for reconnecting clients +- **Fit control source** - Clients can send `FitControl` commands (pause, resume, stop) +- **Graceful shutdown** - Publishes `SessionClosed` on cleanup + +### 3.4 Fit Progress Integration + +In `executor.rs`, when a live session is active: + +```rust +let live_result = builder.fit_with_progress_and_control_in_memory( + |event| { + session.publish_progress(event) // Forward PMcore events to client + }, + || session.next_control(), // Check for client commands +); +``` + +This creates a real-time bridge: PMcore → Rust → TCP → Shiny App → User's browser. + +### 3.5 Live Session R6 Class + +```r +LiveSession <- R6::R6Class("LiveSession", public = list( + initialize = function(host, port, timeout_ms = 5000L), + connect = function(), + close = function(), + wait_connected = function(timeout_ms), + publish_result = function(result_payload, generated_at), + publish_failure = function(message) +)) +``` + +--- + +## 4. Reporting System + +### 4.1 PM_report (`R/PM_report.R`) + +The `PM_report()` function is the entry point for generating reports from a `PM_result`: + +```r +PM_report <- function(x, template, path, show = TRUE, quiet = TRUE) +``` + +**Report modes:** + +| Mode | Behavior | +|------|----------| +| `"app"` | Launches PmetricsReports Shiny app (default) | +| `"plotly"` | Generates legacy Plotly HTML report | +| `"ggplot"` | Generates legacy ggplot HTML report | +| `"ggplot_rust"` | Generates report with rust-backed rendering | +| `"none"` | No-op, returns 0 | + +**Fallback chain:** +1. Try to load `PmetricsReports` package (sibling package) +2. If that fails and mode is `"app"`, fall back to `"ggplot"` HTML generation +3. HTML generation uses Rmd templates from `inst/report/templates/` + +### 4.2 PmetricsReports Package + +Located at `Pmetricsreports/` (sibling to Pmetrics). This is a standalone R package containing: + +- **Shiny app** - Interactive visualization of PM_result objects +- **`run_app()` function** - Entry point, accepts `res`, `launch.browser`, `background`, `live_session` +- **Live session integration** - Connects to the Rust TCP session for real-time updates + +The app resolves via: +```r +pmetrics_path → ../Pmetricsreports/ → pkgload::load_all() → run_app +``` + +### 4.3 Legacy HTML Reports + +When the app is unavailable, Pmetrics falls back to R Markdown templates: + +- `inst/report/templates/plotly.Rmd` - Plotly-based interactive reports +- `inst/report/templates/ggplot.Rmd` - ggplot2-based static reports + +These are rendered using `rmarkdown::render()` to produce standalone HTML files. + +--- + +## 5. Data Flow Summary + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ USER INTERACTION │ +│ R: mod <- PM_model$new(list(pri=..., eqn=..., out=..., err=...)) │ +│ R: data <- PM_data$new("data.csv") │ +└─────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────┐ +│ DSL VALIDATION & COMPILATION │ +│ PM_model$initialize(): │ +│ ├─ Check reserved names │ +│ ├─ Detect model type (ODE vs Analytical) │ +│ ├─ Validate index literals for ODEs │ +│ ├─ Verify required parameters for templates │ +│ └─ PM_model$compile() → stores arg_list │ +└─────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────┐ +│ FIT EXECUTION │ +│ PM_model$fit(data, cycles, prior, points, ...): │ +│ ├─ Validate data/model compatibility │ +│ ├─ Build fit parameters (ranges, error models, prior) │ +│ ├─ extendr → Rust compile_runtime_model() │ +│ │ └─ dsl::compile_module_source_to_runtime() │ +│ │ └─ PMcore compile/ (validation, caches, etc.) │ +│ ├─ Rust executor::fit() → PMcore estimation │ +│ │ ├─ Build NonparametricEstimationProblemBuilder │ +│ │ ├─ Run NPAG/NPOD/POSTPROB algorithm │ +│ │ └─ FitPayload::from_fit_result() │ +│ └─ R: decode_fit_payload() → PM_result │ +└─────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────┐ +│ RESULT CONSTRUCTION │ +│ build_pm_result_from_fit_payload(): │ +│ ├─ PM_pop (population predictions) │ +│ ├─ PM_post (individual posteriors) │ +│ ├─ PM_final (theta, support points) │ +│ ├─ PM_cycle (cycle-by-cycle metrics) │ +│ ├─ PM_op (individual parameters) │ +│ ├─ PM_cov (covariate data) │ +│ └─ save_pm_result_snapshot() → PMout.Rdata │ +└─────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────┐ +│ REPORTING │ +│ PM_report(result, template = "app"): │ +│ ├─ Try PmetricsReports::run_app() (Shiny) │ +│ │ └─ Live session TCP bridge for real-time updates │ +│ └─ Fallback: rmarkdown::render() → HTML │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 6. Key Design Patterns & Observations + +### 6.1 Hybrid R/Rust Architecture + +- **extendr-api** is used for R ↔ Rust FFI (modern replacement for rpy2-style bindings) +- Rust handles: DSL compilation, ODE solving, estimation algorithms, prediction caching +- R handles: User API, data validation, result object construction, reporting +- Communication: JSON serialization for fit payloads, TCP for live sessions + +### 6.2 Compile-Time Dimension Inference + +ODE models require **literal indices** (e.g., `dx[1]`, `x[2]`) rather than dynamic ones (e.g., `dx[i]`). This allows the Rust `ode!` macro to: +- Infer the number of states, drugs, and outputs at compile time +- Generate type-safe, zero-cost abstractions +- Avoid runtime bounds checking + +### 6.3 Three Algorithm Variants + +| Algorithm | Description | Use Case | +|-----------|-------------|----------| +| NPAG | Nonparametric Adaptive Grid | Standard population analysis | +| NPOD | Nonparametric Optimization by Duality | Faster convergence for simpler models | +| POSTPROB | Posterior calculation | Bayesian posteriors with `cycles = 0` | + +All three share the same builder pattern and only differ in their estimation core. + +### 6.4 Prediction Caching + +The `caches.rs` module implements runtime prediction caching during fitting. This avoids recomputing predictions for identical parameter sets across cycles, providing significant speedups (especially in later cycles where parameter values repeat). + +### 6.5 Live Session Protocol + +The live session uses a simple JSON-over-TCP protocol: +- **Server** (Rust): Publishes `FitProgress` events and accepts `FitControl` commands +- **Client** (Shiny): Subscribes to events, displays progress, sends controls +- **Backlog**: 1000-message buffer allows late-connecting clients to catch up + +### 6.6 Result Snapshot System + +Every successful fit saves a `PMout.Rdata` snapshot containing the full result list. This enables: +- Reproducibility (exact state at end of run) +- Fast reloading via `PM_load()` +- Debugging and inspection + +### 6.7 Deprecated Paths + +Several legacy patterns still exist: +- Model text files (`.txt` model definitions) - being replaced by R list definitions +- `PM_fit` class - deprecated in favor of `PM_model$fit()` +- `PM_fit$run()` - deprecated, use `PM_model$fit()` +- Legacy HTML reports - being replaced by PmetricsReports app + +--- + +## 7. File Reference Map + +### R Files + +| File | Role | +|------|------| +| `R/PM_model.R` | PM_model R6 class, model definition, validation, fit() | +| `R/PM_data.R` | PM_data R6 class, data loading and validation | +| `R/PM_fit.R` | PM_fit R6 class (deprecated wrapper) | +| `R/PM_fit_payload.R` | Fit payload decoding, PM_result construction | +| `R/PM_cycle.R` | Cycle information R6 object | +| `R/PM_live.R` | Live session R6 class, TCP communication | +| `R/PM_report.R` | Report generation entry point | +| `R/model_dsl.R` | DSL AST analysis utilities | +| `R/mod_lib.R` | Model library templates and browsing | +| `R/PM_parse.R` | Parser for Rust NPAG output files | +| `R/PM_result.R` | PM_result R6 class (result container) | +| `R/PM_pop.R` | Population predictions R6 object | +| `R/PM_post.R` | Posterior estimates R6 object | +| `R/PM_final.R` | Final theta/support points R6 object | +| `R/PM_op.R` | Individual parameters R6 object | +| `R/PM_cov.R` | Covariate data R6 object | + +### Rust Files (Pmetrics crate) + +| File | Role | +|------|------| +| `src/rust/src/lib.rs` | extendr entry points, compile_runtime_model, simulate | +| `src/rust/src/executor.rs` | fit() orchestration, live session integration | +| `src/rust/src/settings.rs` | R parameter → PMcore builder translation | +| `src/rust/src/fit_payload.rs` | FitResult → JSON serialization | +| `src/rust/src/live.rs` | Live session TCP server, message protocol | +| `src/rust/src/simulation.rs` | Simulation row construction | +| `src/rust/src/logs.rs` | Tracing/log configuration | + +### Rust Files (PMcore crate) + +| Path | Role | +|------|------| +| `src/compile/` | DSL compilation, validation, caching | +| `src/model/` | Equation types, parameter space, covariate models | +| `src/estimation/nonparametric/` | NPAG algorithm implementation | +| `src/estimation/parametric/` | NPOD algorithm implementation | +| `src/api/` | Public API: estimation problem builder, progress, fit control | +| `src/results/` | FitResult, predictions, summary, diagnostics | +| `src/routines/initialization/` | Support point initialization (Sobol, etc.) | + +--- + +## 8. Potential Improvements + +1. **Remove deprecated paths** - Clean up `PM_fit`, model text files, legacy HTML templates +2. **Better error messages** - DSL compilation errors could include line numbers and context +3. **Parallel compilation** - Multiple models could be compiled in parallel +4. **Cached compilation** - DSL compilation results could be cached by source hash +5. **Live session persistence** - Live sessions could survive R session restarts +6. **Typed fit payload** - Instead of JSON → tibble, use a binary format (e.g., Arrow) for performance +7. **Model versioning** - Track model DSL schema versions for backward compatibility diff --git a/Cargo.lock b/Cargo.lock index c0f0ece13..3e7a7a087 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1666,10 +1666,14 @@ name = "pm_rs" version = "0.1.0" dependencies = [ "anyhow", + "csv", "extendr-api", "extendr-engine", "pmcore", "rayon", + "serde", + "serde_json", + "sha2", "tracing", "tracing-subscriber", ] @@ -1677,7 +1681,6 @@ dependencies = [ [[package]] name = "pmcore" version = "0.25.2" -source = "git+https://github.com/LAPKB/PMcore?rev=668dd305436c75d4fdef29138261540c114c0c34#668dd305436c75d4fdef29138261540c114c0c34" dependencies = [ "anyhow", "argmin", diff --git a/Cargo.toml b/Cargo.toml index 9e3b68877..a74884bb3 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,6 @@ exclude = [ [profile.release] panic = "unwind" + +[patch."https://github.com/LAPKB/PMcore"] +pmcore = { path = "../PMcore-structure" } diff --git a/DESCRIPTION b/DESCRIPTION index 95a323e14..65e7c3098 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -79,7 +79,7 @@ Suggests: VignetteBuilder: knitr Config/Needs/website: r-lib/pkgdown, quarto -Config/rextendr/version: 0.4.2 +Config/rextendr/version: 0.5.0 Config/roxygen2/version: 8.0.0 Config/testthat/edition: 3 Encoding: UTF-8 diff --git a/NAMESPACE b/NAMESPACE index 12404910d..8b6018717 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -102,6 +102,7 @@ export(check_updates) export(clear_build) export(cli_ask) export(cli_df) +export(close_live_session) export(click_plot) export(cor2cov) export(create_pmetrics_project) @@ -145,6 +146,7 @@ export(round2) export(save_flextable) export(setPMoptions) export(setup_logs) +export(start_live_session) export(simulate_all) export(simulate_one) export(ss.PK) @@ -159,6 +161,7 @@ export(two_comp_bolus_cl) export(two_comp_iv) export(two_comp_iv_cl) export(validate_model_source) +export(wait_live_session_connected) export(zBMI) importFrom(R6,R6Class) importFrom(bslib,accordion) diff --git a/R/PM_bridge_errors.R b/R/PM_bridge_errors.R new file mode 100644 index 000000000..aa3e5677e --- /dev/null +++ b/R/PM_bridge_errors.R @@ -0,0 +1,198 @@ +pm_bridge_error_prefix <- "PMETRICS_BRIDGE_ERROR=" + +pm_bridge_scalar <- function(x) { + if (is.null(x) || !length(x)) { + return(NULL) + } + + x[[1]] +} + +pm_bridge_first_or <- function(x, default = NULL) { + value <- pm_bridge_scalar(x) + if (is.null(value)) { + return(default) + } + + value +} + +pm_bridge_stage <- function(stage, default_stage = "runtime") { + stage <- pm_bridge_scalar(stage) + if (is.null(stage) || !nzchar(as.character(stage))) { + return(default_stage) + } + + stage <- tolower(as.character(stage)) + if (!stage %in% c("compile", "data", "settings", "runtime", "handoff")) { + return(default_stage) + } + + stage +} + +parse_pm_bridge_error <- function(message) { + message <- as.character(message[[1]]) + if (!nzchar(message)) { + return(list( + structured = FALSE, + kind = "error", + schema_version = NULL, + stage = NULL, + code = NULL, + message = "", + diagnostic = NULL, + details = list() + )) + } + + prefix_loc <- regexpr(pm_bridge_error_prefix, message, fixed = TRUE)[[1]] + if (is.na(prefix_loc) || prefix_loc < 1) { + return(list( + structured = FALSE, + kind = "error", + schema_version = NULL, + stage = NULL, + code = NULL, + message = message, + diagnostic = NULL, + details = list() + )) + } + + display_message <- sub("[\r\n]+$", "", substr(message, 1, prefix_loc - 1)) + payload_text <- substr(message, prefix_loc + nchar(pm_bridge_error_prefix), nchar(message)) + payload <- tryCatch( + jsonlite::fromJSON(payload_text, simplifyVector = FALSE), + error = function(e) NULL + ) + + if (!is.list(payload)) { + return(list( + structured = FALSE, + kind = "error", + schema_version = NULL, + stage = NULL, + code = NULL, + message = if (nzchar(display_message)) display_message else message, + diagnostic = NULL, + details = list() + )) + } + + details <- payload$details + if (!is.list(details)) { + details <- list() + } + + diagnostic <- pm_bridge_scalar(payload$diagnostic) + if (!is.null(diagnostic)) { + diagnostic <- as.character(diagnostic) + } + + list( + structured = TRUE, + kind = as.character(pm_bridge_first_or(payload$kind, "error")), + schema_version = pm_bridge_scalar(payload$schema_version), + stage = pm_bridge_stage(payload$stage), + code = as.character(pm_bridge_first_or(payload$code, "bridge_error")), + message = if (nzchar(display_message)) { + display_message + } else { + as.character(pm_bridge_first_or(payload$message, message)) + }, + diagnostic = diagnostic, + details = details + ) +} + +pm_bridge_error_classes <- function(stage, structured = TRUE) { + stage_class <- paste0("pmetrics_bridge_", pm_bridge_stage(stage), "_error") + if (isTRUE(structured)) { + return(c(stage_class, "pmetrics_bridge_error")) + } + + c(stage_class, "pmetrics_bridge_unstructured_error", "pmetrics_bridge_error") +} + +abort_pm_bridge_message <- function( + message, + stage, + code, + diagnostic = NULL, + details = list(), + parent = NULL, + structured = TRUE, + schema_version = NULL +) { + message <- as.character(pm_bridge_first_or(message, "")) + if (!nzchar(message) && !is.null(diagnostic)) { + message <- as.character(diagnostic) + } + + if (!is.list(details)) { + details <- list() + } + + rlang::abort( + message = message, + class = pm_bridge_error_classes(stage, structured = structured), + stage = pm_bridge_stage(stage), + code = as.character(pm_bridge_first_or(code, "bridge_error")), + diagnostic = if (is.null(diagnostic)) NULL else as.character(diagnostic), + details = details, + structured = isTRUE(structured), + schema_version = schema_version, + parent = parent + ) +} + +abort_pm_bridge_error <- function( + error, + default_stage = "runtime", + default_code = "bridge_error", + details = list() +) { + bridge_error <- parse_pm_bridge_error(conditionMessage(error)) + bridge_details <- bridge_error$details + if (!is.list(bridge_details)) { + bridge_details <- list() + } + if (is.list(details) && length(details)) { + bridge_details <- utils::modifyList(bridge_details, details) + } + + abort_pm_bridge_message( + message = bridge_error$message, + stage = if (is.null(bridge_error$stage)) default_stage else bridge_error$stage, + code = if (is.null(bridge_error$code)) default_code else bridge_error$code, + diagnostic = bridge_error$diagnostic, + details = bridge_details, + parent = error, + structured = isTRUE(bridge_error$structured), + schema_version = bridge_error$schema_version + ) +} + +call_pm_bridge <- function(code, default_stage = "runtime", default_code = "bridge_error", details = list()) { + tryCatch( + code, + error = function(error) { + abort_pm_bridge_error( + error, + default_stage = default_stage, + default_code = default_code, + details = details + ) + } + ) +} + +utils::globalVariables(c( + "call_pm_bridge", + "close_live_session", + "publish_live_report_failed", + "publish_live_report_result", + "start_live_session", + "wait_live_session_connected" +)) \ No newline at end of file diff --git a/R/PM_cov.R b/R/PM_cov.R index 1b10453db..87550d3f8 100755 --- a/R/PM_cov.R +++ b/R/PM_cov.R @@ -94,31 +94,14 @@ PM_cov <- R6::R6Class( ), # end public private = list( make = function(data, path) { - if (file.exists(file.path(path, "posterior.csv"))) { - posts <- readr::read_csv(file = file.path(path, "posterior.csv"), show_col_types = FALSE) - } else if (inherits(data, "PM_cov") & !is.null(data$data)) { # file not there, and already PM_cov + if (inherits(data, "PM_cov") & !is.null(data$data)) { # file not there, and already PM_cov class(data$data) <- c("PM_cov_data", "data.frame") return(data$data) - } else { - cli::cli_warn(c( - "!" = "Unable to generate covariate-posterior information.", - "i" = "{.file {file.path(path, 'posterior.csv')}} does not exist, and result does not have valid {.code PM_cov} object." - )) - return(NULL) } - if (file.exists(file.path(path, "covariates.csv"))) { - covs <- readr::read_csv(file = file.path(path, "covariates.csv"), show_col_types = FALSE) - } else if (inherits(data, "PM_cov") && !is.null(data$data)) { # file not there, and already PM_cov - class(data$data) <- c("PM_cov_data", "data.frame") - return(data$data) - } else { - cli::cli_warn(c( - "!" = "Unable to generate covariate-posterior information.", - "i" = "{.file {file.path(path, 'covariates.csv')}} does not exist, and result does not have valid {.code PM_cov} object." - )) - return(NULL) - } + fit_payload <- fit_payload_from_source(data, path) + posts <- tibble::as_tibble(fit_payload$posterior) + covs <- tibble::as_tibble(fit_payload$covariates) covs <- covs |> mutate(block = block + 1) diff --git a/R/PM_cycle.R b/R/PM_cycle.R index 3e7be4e95..70eeb6757 100755 --- a/R/PM_cycle.R +++ b/R/PM_cycle.R @@ -119,63 +119,16 @@ PM_cycle <- R6::R6Class( ), # end active private = list( make = function(data, path) { - if (file.exists(file.path(path, "iterations.csv"))) { - raw <- readr::read_csv(file = file.path(path, "iterations.csv"), show_col_types = FALSE) - if (nrow(raw) == 0) { # posterior - raw <- data.frame(cycle = 0, status = "Posterior") - write.csv(raw, file.path(path, "iterations.csv"), row.names = FALSE) - } - } else if (inherits(data, "PM_cycle") & !is.null(data$data)) { # file not there, and already PM_cycle + config <- NULL + if (inherits(data, "PM_cycle") & !is.null(data$data)) { # file not there, and already PM_cycle class(data$data) <- c("PM_cycle_data", "list") return(data$data) - } else { - cli::cli_warn(c( - "!" = "Unable to generate cycle information.", - "i" = "{.file {file.path(path, 'iterations.csv')}} does not exist, and result does not have valid {.code PM_cycle} object." - )) - return(NULL) } - - if (file.exists(file.path(path, "predictions.csv"))) { - op_raw <- readr::read_csv( - file = file.path(path, "predictions.csv"), - col_types = list( - time = readr::col_double(), - outeq = readr::col_integer(), - block = readr::col_integer(), - obs = readr::col_double(), - cens = readr::col_character(), - pop_mean = readr::col_double(), - pop_median = readr::col_double(), - post_mean = readr::col_double(), - post_median = readr::col_double() - ), show_col_types = FALSE - ) |> filter(!is.na(obs)) - } else if (inherits(data, "PM_cycle")) { # file not there, and already PM_op - class(data$data) <- c("PM_cycle_data", "list") - return(data$data) - } else { - cli::cli_warn(c( - "!" = "Unable to generate cycle information.", - "i" = "{.file {file.path(path, 'predictions.csv')}} does not exist, and result does not have valid {.code PM_cycle} object." - )) - return(NULL) - } - - - if (file.exists(file.path(path, "settings.json"))) { - config <- jsonlite::fromJSON(file.path(path, "settings.json")) - } else if (inherits(data, "PM_cycle") & !is.null(data$data)) { # file not there, and already PM_op - class(data$data) <- c("PM_cycle_data", "list") - return(data$data) - } else { - cli::cli_warn(c( - "!" = "Unable to generate cycle information.", - "i" = "{.file {file.path(path, 'settings.json')}} does not exist, and result does not have valid {.code PM_cycle} object." - )) - return(NULL) - } + fit_payload <- fit_payload_from_source(data, path) + raw <- tibble::as_tibble(fit_payload$iterations) + op_raw <- tibble::as_tibble(fit_payload$predictions) |> filter(!is.na(obs)) + config <- fit_payload$config parameter_names <- if (!is.null(config$parameters) && length(config$parameters) > 0) { config$parameters[[1]][["name"]] diff --git a/R/PM_final.R b/R/PM_final.R index 37bc9d6c0..fbff372f7 100755 --- a/R/PM_final.R +++ b/R/PM_final.R @@ -230,44 +230,16 @@ PM_final <- R6::R6Class( ), # end active private = list( make = function(data, path) { - if (file.exists(file.path(path, "theta.csv"))) { - theta <- readr::read_csv(file = file.path(path, "theta.csv"), show_col_types = FALSE) - } else if (inherits(data, "PM_final") & !is.null(data$data)) { # file not there, and already PM_final - class(data$data) <- c("PM_final_data", "list") + config <- NULL + if (inherits(data, "PM_final") & !is.null(data$data)) { # file not there, and already PM_final + class(data$data) <- c("PM_final_data", "NPAG", "list") return(data$data) - } else { - cli::cli_warn(c( - "!" = "Unable to generate final cycle information.", - "i" = "{.file {file.path(path, 'theta.csv')}} does not exist, and result does not have valid {.code PM_final} object." - )) - return(NULL) - } - - if (file.exists(file.path(path, "posterior.csv"))) { - post <- readr::read_csv(file = file.path(path, "posterior.csv"), show_col_types = FALSE) - } else if (inherits(data, "PM_final") & !is.null(data$data)) { # file not there, and already PM_final - class(data$data) <- c("PM_final_data", "data.frame") - return(data$data) - } else { - cli::cli_warn(c( - "!" = "Unable to generate final cycle information.", - "i" = "{.file {file.path(path, 'posterior.csv')}} does not exist, and result does not have valid {.code PM_final} object." - )) - return(NULL) } - if (file.exists(file.path(path, "settings.json"))) { - config <- jsonlite::fromJSON(file.path(path, "settings.json")) - } else if (inherits(data, "PM_final") & !is.null(data$data)) { # file not there, and already PM_final - class(data$data) <- c("PM_final_data", "data.frame") - return(data$data) - } else { - cli::cli_warn(c( - "!" = "Unable to generate final cycle information.", - "i" = "{.file {file.path(path, 'settings.json')}} does not exist, and result does not have valid {.code PM_final} object." - )) - return(NULL) - } + fit_payload <- fit_payload_from_source(data, path) + theta <- tibble::as_tibble(fit_payload$theta) + post <- tibble::as_tibble(fit_payload$posterior) + config <- fit_payload$config par_names <- names(theta)[names(theta) != "prob"] diff --git a/R/PM_fit_payload.R b/R/PM_fit_payload.R new file mode 100644 index 000000000..d08e6983d --- /dev/null +++ b/R/PM_fit_payload.R @@ -0,0 +1,845 @@ +pm_fit_payload_schema_version <- 1L +pm_fit_manifest_file_name <- "fit_manifest.json" + +pm_fit_payload_column_spec <- function(type, required = TRUE, default = NULL) { + list(type = type, required = required, default = default) +} + +pm_fit_payload_named_specs <- function(columns, type, required = TRUE, default = NULL) { + if (!length(columns)) { + return(list()) + } + + stats::setNames( + rep(list(pm_fit_payload_column_spec(type, required = required, default = default)), length(columns)), + columns + ) +} + +pm_fit_payload_empty_column <- function(type) { + switch(type, + numeric = numeric(), + integer = integer(), + character = character(), + logical = logical(), + cli::cli_abort(c("x" = "Unknown fit payload schema type {.val {type}}.")) + ) +} + +pm_fit_payload_empty_table <- function(column_specs) { + tibble::new_tibble( + stats::setNames( + lapply(column_specs, function(spec) pm_fit_payload_empty_column(spec$type)), + names(column_specs) + ), + nrow = 0L + ) +} + +pm_fit_payload_schema_classes <- function(class = NULL) { + unique(c(class, "pmetrics_result_schema_error")) +} + +abort_pm_fit_payload_schema <- function( + message, + class = NULL, + table = NULL, + column = NULL, + source = NULL, + path = NULL, + expected = NULL +) { + rlang::abort( + message = message, + class = pm_fit_payload_schema_classes(class), + table = table, + column = column, + source = source, + path = path, + expected = expected + ) +} + +pm_fit_payload_parameter_stat_columns <- function(parameter_names) { + unlist(lapply(parameter_names, function(name) { + c(paste0(name, ".mean"), paste0(name, ".median"), paste0(name, ".sd")) + }), use.names = FALSE) +} + +pm_fit_payload_table_specs <- function(parameter_names, covariate_names, error_model_count) { + iteration_specs <- c( + list( + cycle = pm_fit_payload_column_spec("integer"), + converged = pm_fit_payload_column_spec("logical"), + status = pm_fit_payload_column_spec("character"), + neg2ll = pm_fit_payload_column_spec("numeric"), + nspp = pm_fit_payload_column_spec("integer") + ), + pm_fit_payload_named_specs( + paste0("gamlam.", seq_len(error_model_count) - 1L), + "numeric" + ), + pm_fit_payload_named_specs(pm_fit_payload_parameter_stat_columns(parameter_names), "numeric") + ) + + list( + iterations = iteration_specs, + theta = c( + pm_fit_payload_named_specs(parameter_names, "numeric"), + list(prob = pm_fit_payload_column_spec("numeric")) + ), + posterior = c( + list( + id = pm_fit_payload_column_spec("character"), + point = pm_fit_payload_column_spec("integer") + ), + pm_fit_payload_named_specs(parameter_names, "numeric"), + list(prob = pm_fit_payload_column_spec("numeric")) + ), + predictions = list( + id = pm_fit_payload_column_spec("character"), + time = pm_fit_payload_column_spec("numeric"), + outeq = pm_fit_payload_column_spec("integer"), + block = pm_fit_payload_column_spec("integer"), + obs = pm_fit_payload_column_spec("numeric"), + cens = pm_fit_payload_column_spec("character", required = FALSE, default = "none"), + pop_mean = pm_fit_payload_column_spec("numeric"), + pop_median = pm_fit_payload_column_spec("numeric"), + post_mean = pm_fit_payload_column_spec("numeric"), + post_median = pm_fit_payload_column_spec("numeric") + ), + covariates = c( + list( + id = pm_fit_payload_column_spec("character"), + time = pm_fit_payload_column_spec("numeric"), + block = pm_fit_payload_column_spec("integer") + ), + pm_fit_payload_named_specs(covariate_names, "numeric") + ) + ) +} + +pm_fit_payload_default_column <- function(spec, nrow) { + default_value <- if (is.null(spec$default)) { + rep(NA, nrow) + } else { + rep(spec$default, nrow) + } + + switch(spec$type, + numeric = as.numeric(default_value), + integer = as.integer(default_value), + character = as.character(default_value), + logical = as.logical(default_value), + cli::cli_abort(c("x" = "Unknown fit payload schema type {.val {spec$type}}.")) + ) +} + +pm_fit_payload_scalar_input <- function(value) { + if (is.factor(value)) { + return(as.character(value)) + } + + if (is.list(value)) { + return(vapply(value, function(item) paste(as.character(item), collapse = ","), character(1))) + } + + value +} + +pm_fit_payload_as_numeric <- function(value, table, column, source, path) { + raw_value <- pm_fit_payload_scalar_input(value) + numeric_value <- suppressWarnings(as.numeric(raw_value)) + invalid <- !is.na(raw_value) & is.na(numeric_value) + + if (any(invalid)) { + abort_pm_fit_payload_schema( + message = paste0("Table `", table, "` column `", column, "` contains values that cannot be read as numeric."), + class = "pmetrics_result_schema_coercion_error", + table = table, + column = column, + source = source, + path = path, + expected = "numeric" + ) + } + + numeric_value +} + +pm_fit_payload_as_integer <- function(value, table, column, source, path) { + numeric_value <- pm_fit_payload_as_numeric(value, table, column, source, path) + invalid <- !is.na(numeric_value) & numeric_value != trunc(numeric_value) + + if (any(invalid)) { + abort_pm_fit_payload_schema( + message = paste0("Table `", table, "` column `", column, "` contains non-integer values."), + class = "pmetrics_result_schema_coercion_error", + table = table, + column = column, + source = source, + path = path, + expected = "integer" + ) + } + + as.integer(numeric_value) +} + +pm_fit_payload_as_logical <- function(value, table, column, source, path) { + if (is.logical(value)) { + return(value) + } + + raw_value <- pm_fit_payload_scalar_input(value) + text_value <- trimws(tolower(as.character(raw_value))) + logical_value <- rep(NA, length(text_value)) + logical_value[text_value %in% c("true", "t", "1")] <- TRUE + logical_value[text_value %in% c("false", "f", "0")] <- FALSE + logical_value[is.na(raw_value)] <- NA + + invalid <- !is.na(raw_value) & is.na(logical_value) + if (any(invalid)) { + abort_pm_fit_payload_schema( + message = paste0("Table `", table, "` column `", column, "` contains values that cannot be read as logical."), + class = "pmetrics_result_schema_coercion_error", + table = table, + column = column, + source = source, + path = path, + expected = "logical" + ) + } + + logical_value +} + +pm_fit_payload_coerce_column <- function(value, table, column, spec, source, path = NULL) { + switch(spec$type, + numeric = pm_fit_payload_as_numeric(value, table, column, source, path), + integer = pm_fit_payload_as_integer(value, table, column, source, path), + logical = pm_fit_payload_as_logical(value, table, column, source, path), + character = as.character(pm_fit_payload_scalar_input(value)), + cli::cli_abort(c("x" = "Unknown fit payload schema type {.val {spec$type}}.")) + ) +} + +pm_fit_payload_posterior_iterations_row <- function(column_specs) { + values <- lapply(names(column_specs), function(column_name) { + if (column_name == "cycle") { + return(0L) + } + if (column_name == "converged") { + return(NA) + } + if (column_name == "status") { + return("Posterior") + } + + pm_fit_payload_default_column(column_specs[[column_name]], 1L) + }) + + tibble::as_tibble(stats::setNames(values, names(column_specs))) +} + +pm_fit_payload_normalize_table <- function( + table_name, + data, + column_specs, + source, + path = NULL, + empty_table = NULL +) { + if (is.null(data)) { + abort_pm_fit_payload_schema( + message = paste0("Missing required table `", table_name, "` in ", source, "."), + class = "pmetrics_result_schema_missing_table_error", + table = table_name, + source = source, + path = path + ) + } + + table <- if (length(data) == 0) { + pm_fit_payload_empty_table(column_specs) + } else { + tibble::as_tibble(data) + } + + required_columns <- names(column_specs)[vapply(column_specs, function(spec) isTRUE(spec$required), logical(1))] + missing_columns <- setdiff(required_columns, names(table)) + if (length(missing_columns)) { + abort_pm_fit_payload_schema( + message = paste0( + "Table `", table_name, "` is missing required column `", missing_columns[[1]], "`." + ), + class = "pmetrics_result_schema_missing_field_error", + table = table_name, + column = missing_columns[[1]], + source = source, + path = path + ) + } + + optional_columns <- setdiff(names(column_specs), names(table)) + for (column_name in optional_columns) { + table[[column_name]] <- pm_fit_payload_default_column(column_specs[[column_name]], nrow(table)) + } + + table <- table[, names(column_specs), drop = FALSE] + + for (column_name in names(column_specs)) { + table[[column_name]] <- pm_fit_payload_coerce_column( + value = table[[column_name]], + table = table_name, + column = column_name, + spec = column_specs[[column_name]], + source = source, + path = path + ) + } + + if (nrow(table) == 0 && is.function(empty_table)) { + table <- empty_table(column_specs) + } + + table +} + +pm_fit_payload_table_names <- function(data, base_columns) { + if (is.null(data) || length(data) == 0) { + return(character()) + } + + setdiff(names(tibble::as_tibble(data)), base_columns) +} + +pm_fit_payload_validate_config <- function(config, source, path = NULL) { + if (!is.list(config)) { + abort_pm_fit_payload_schema( + message = paste0("Missing fit settings in ", source, "."), + class = "pmetrics_result_schema_missing_table_error", + table = "settings", + source = source, + path = path + ) + } + + if (is.null(config$parameters) || length(config$parameters) == 0 || is.null(config$parameters[[1]][["name"]])) { + abort_pm_fit_payload_schema( + message = paste0("Settings are missing required field `parameters[[1]]$name` in ", source, "."), + class = "pmetrics_result_schema_missing_field_error", + table = "settings", + column = "parameters[[1]]$name", + source = source, + path = path + ) + } + + if (is.null(config$errormodels) || is.null(config$errormodels$models)) { + abort_pm_fit_payload_schema( + message = paste0("Settings are missing required field `errormodels$models` in ", source, "."), + class = "pmetrics_result_schema_missing_field_error", + table = "settings", + column = "errormodels$models", + source = source, + path = path + ) + } + + config +} + +pm_fit_payload_parameter_names <- function(parsed, fit_params) { + if (!is.null(parsed$parameter_names)) { + return(as.character(parsed$parameter_names)) + } + + names(fit_params$ranges) +} + +pm_fit_payload_covariate_names <- function(parsed) { + if (!is.null(parsed$covariate_names)) { + return(as.character(parsed$covariate_names)) + } + + pm_fit_payload_table_names(parsed$covariates, c("id", "time", "block")) +} + +pm_fit_payload_build <- function( + parameter_names, + covariate_names, + config, + iterations, + theta, + posterior, + predictions, + covariates, + transport, + source, + path = NULL +) { + config <- pm_fit_payload_validate_config(config, source = source, path = path) + specs <- pm_fit_payload_table_specs( + parameter_names = parameter_names, + covariate_names = covariate_names, + error_model_count = length(config$errormodels$models) + ) + + structure( + list( + schema_version = pm_fit_payload_schema_version, + transport = transport, + parameter_names = parameter_names, + covariate_names = covariate_names, + iterations = pm_fit_payload_normalize_table( + table_name = "iterations", + data = iterations, + column_specs = specs$iterations, + source = source, + path = path, + empty_table = pm_fit_payload_posterior_iterations_row + ), + theta = pm_fit_payload_normalize_table( + table_name = "theta", + data = theta, + column_specs = specs$theta, + source = source, + path = path + ), + posterior = pm_fit_payload_normalize_table( + table_name = "posterior", + data = posterior, + column_specs = specs$posterior, + source = source, + path = path + ), + predictions = pm_fit_payload_normalize_table( + table_name = "predictions", + data = predictions, + column_specs = specs$predictions, + source = source, + path = path + ), + covariates = pm_fit_payload_normalize_table( + table_name = "covariates", + data = covariates, + column_specs = specs$covariates, + source = source, + path = path + ), + config = config + ), + class = "PM_fit_payload" + ) +} + +build_fit_payload_config <- function(fit_params) { + parameter_ranges <- purrr::imap_dfr(fit_params$ranges, function(bounds, name) { + bounds <- as.numeric(bounds) + tibble::tibble(name = name, lower = bounds[[1]], upper = bounds[[2]]) + }) + + error_models <- purrr::map(fit_params$error_models, function(model) { + coeff <- as.numeric(model$coeff) + model_type <- as.character(model$type[[1]]) + + stats::setNames( + list(list(poly = list(c0 = coeff[[1]], c1 = coeff[[2]], c2 = coeff[[3]], c3 = coeff[[4]]))), + model_type + ) + }) + + prior <- NULL + if (is.character(fit_params$prior) && identical(tolower(fit_params$prior), "sobol")) { + prior <- list(Sobol = c(as.numeric(fit_params$points))) + } + + list( + parameters = list(parameter_ranges), + errormodels = list(models = error_models), + prior = prior + ) +} + +decode_fit_payload <- function(payload_json, fit_params) { + parsed <- jsonlite::fromJSON(payload_json) + parameter_names <- pm_fit_payload_parameter_names(parsed, fit_params) + covariate_names <- pm_fit_payload_covariate_names(parsed) + + pm_fit_payload_build( + parameter_names = parameter_names, + covariate_names = covariate_names, + config = build_fit_payload_config(fit_params), + iterations = parsed$iterations, + theta = parsed$theta, + posterior = parsed$posterior, + predictions = parsed$predictions, + covariates = parsed$covariates, + transport = "inline", + source = "inline fit payload" + ) +} + +pm_fit_payload_parameter_names_from_config <- function(config, source, path = NULL) { + parameters <- config$parameters[[1]] + parameter_names <- parameters[["name"]] + + if (is.null(parameter_names)) { + abort_pm_fit_payload_schema( + message = paste0("Settings are missing required field `parameters[[1]]$name` in ", source, "."), + class = "pmetrics_result_schema_missing_field_error", + table = "settings", + column = "parameters[[1]]$name", + source = source, + path = path + ) + } + + as.character(parameter_names) +} + +read_fit_result_envelope <- function(payload_json, source, path = NULL) { + parsed <- jsonlite::fromJSON(payload_json) + + if (!is.list(parsed) || is.null(parsed$kind) || !identical(as.character(parsed$kind[[1]]), "ok")) { + abort_pm_fit_payload_schema( + message = paste0("Fit result envelope must include `kind = \"ok\"` in ", source, "."), + class = "pmetrics_result_schema_missing_field_error", + table = "fit_result", + column = "kind", + source = source, + path = path + ) + } + + schema_version <- suppressWarnings(as.integer(parsed$schema_version[[1]] %||% NA_integer_)) + if (length(schema_version) != 1L || is.na(schema_version) || !identical(schema_version, pm_fit_payload_schema_version)) { + abort_pm_fit_payload_schema( + message = paste0( + "Unsupported fit result schema version `", + parsed$schema_version[[1]] %||% "", + "` in ", + source, + ". Expected `", + pm_fit_payload_schema_version, + "`." + ), + class = "pmetrics_result_schema_version_error", + table = "fit_result", + source = source, + path = path + ) + } + + transport <- as.character(parsed$transport[[1]] %||% NA_character_) + if (length(transport) != 1L || is.na(transport) || !(transport %in% c("inline", "manifest"))) { + abort_pm_fit_payload_schema( + message = paste0("Unsupported fit result transport `", transport, "` in ", source, "."), + class = "pmetrics_result_schema_missing_field_error", + table = "fit_result", + column = "transport", + source = source, + path = path + ) + } + + if (is.null(parsed$config)) { + abort_pm_fit_payload_schema( + message = paste0("Fit result envelope is missing required field `config` in ", source, "."), + class = "pmetrics_result_schema_missing_field_error", + table = "fit_result", + column = "config", + source = source, + path = path + ) + } + + if (identical(transport, "inline") && is.null(parsed$payload)) { + abort_pm_fit_payload_schema( + message = paste0("Fit result envelope is missing required field `payload` in ", source, "."), + class = "pmetrics_result_schema_missing_field_error", + table = "fit_result", + column = "payload", + source = source, + path = path + ) + } + + if (identical(transport, "manifest") && is.null(parsed$manifest)) { + abort_pm_fit_payload_schema( + message = paste0("Fit result envelope is missing required field `manifest` in ", source, "."), + class = "pmetrics_result_schema_missing_field_error", + table = "fit_result", + column = "manifest", + source = source, + path = path + ) + } + + list( + transport = transport, + config = pm_fit_payload_validate_config(parsed$config, source = source, path = path), + payload = parsed$payload, + manifest = parsed$manifest + ) +} + +read_fit_manifest_table <- function(path, manifest, table_name, source) { + if (is.null(manifest$tables) || is.null(manifest$tables[[table_name]])) { + abort_pm_fit_payload_schema( + message = paste0("Fit manifest is missing required table metadata `", table_name, "` in ", source, "."), + class = "pmetrics_result_schema_missing_table_error", + table = table_name, + source = source, + path = path + ) + } + + table_manifest <- manifest$tables[[table_name]] + file_name <- as.character(table_manifest$path[[1]] %||% NA_character_) + row_count <- suppressWarnings(as.integer(table_manifest$row_count[[1]] %||% NA_integer_)) + sha256 <- as.character(table_manifest$sha256[[1]] %||% NA_character_) + + if (length(file_name) != 1L || is.na(file_name) || identical(file_name, "")) { + abort_pm_fit_payload_schema( + message = paste0("Fit manifest is missing required field `path` for table `", table_name, "` in ", source, "."), + class = "pmetrics_result_schema_missing_field_error", + table = table_name, + column = "path", + source = source, + path = path + ) + } + + if (length(row_count) != 1L || is.na(row_count)) { + abort_pm_fit_payload_schema( + message = paste0("Fit manifest is missing required field `row_count` for table `", table_name, "` in ", source, "."), + class = "pmetrics_result_schema_missing_field_error", + table = table_name, + column = "row_count", + source = source, + path = path + ) + } + + if (length(sha256) != 1L || is.na(sha256) || identical(sha256, "")) { + abort_pm_fit_payload_schema( + message = paste0("Fit manifest is missing required field `sha256` for table `", table_name, "` in ", source, "."), + class = "pmetrics_result_schema_missing_field_error", + table = table_name, + column = "sha256", + source = source, + path = path + ) + } + + table <- read_fit_payload_table(path, file_name, table_name) + + if (!identical(nrow(table), row_count)) { + abort_pm_fit_payload_schema( + message = paste0( + "Fit manifest row count mismatch for table `", + table_name, + "` in ", + source, + ": expected ", + row_count, + ", got ", + nrow(table), + "." + ), + class = "pmetrics_result_schema_coercion_error", + table = table_name, + column = "row_count", + source = source, + path = path + ) + } + + table +} + +read_fit_payload_from_manifest <- function(manifest_payload, path, source = "fit manifest") { + manifest <- manifest_payload$manifest + config <- manifest_payload$config + parameter_names <- pm_fit_payload_parameter_names_from_config(config, source = source, path = path) + covariates <- read_fit_manifest_table(path, manifest, "covariates", source) + covariate_names <- pm_fit_payload_table_names(covariates, c("id", "time", "block")) + + pm_fit_payload_build( + parameter_names = parameter_names, + covariate_names = covariate_names, + config = config, + iterations = read_fit_manifest_table(path, manifest, "iterations", source), + theta = read_fit_manifest_table(path, manifest, "theta", source), + posterior = read_fit_manifest_table(path, manifest, "posterior", source), + predictions = read_fit_manifest_table(path, manifest, "predictions", source), + covariates = covariates, + transport = "manifest", + source = source, + path = path + ) +} + +decode_fit_result_envelope <- function(payload_json, path = NULL) { + parsed <- read_fit_result_envelope( + payload_json = payload_json, + source = "fit result envelope", + path = path + ) + + if (identical(parsed$transport, "inline")) { + payload <- parsed$payload + parameter_names <- if (!is.null(payload$parameter_names)) { + as.character(payload$parameter_names) + } else { + pm_fit_payload_parameter_names_from_config(parsed$config, source = "fit result envelope", path = path) + } + covariate_names <- pm_fit_payload_covariate_names(payload) + + return(pm_fit_payload_build( + parameter_names = parameter_names, + covariate_names = covariate_names, + config = parsed$config, + iterations = payload$iterations, + theta = payload$theta, + posterior = payload$posterior, + predictions = payload$predictions, + covariates = payload$covariates, + transport = "inline", + source = "fit result envelope", + path = path + )) + } + + read_fit_payload_from_manifest(parsed, path = path, source = "fit result envelope") +} + +read_fit_payload_from_outputs <- function(path) { + settings_path <- file.path(path, "settings.json") + if (!file.exists(settings_path)) { + abort_pm_fit_payload_schema( + message = paste0("Missing required settings file `", settings_path, "`."), + class = "pmetrics_result_schema_missing_table_error", + table = "settings", + source = "output files", + path = path + ) + } + + config <- pm_fit_payload_validate_config( + jsonlite::fromJSON(settings_path), + source = "output files", + path = path + ) + parameter_names <- as.character(config$parameters[[1]][["name"]]) + covariates <- read_fit_payload_table(path, "covariates.csv", "covariates") + covariate_names <- pm_fit_payload_table_names(covariates, c("id", "time", "block")) + + pm_fit_payload_build( + parameter_names = parameter_names, + covariate_names = covariate_names, + config = config, + iterations = read_fit_payload_table(path, "iterations.csv", "iterations"), + theta = read_fit_payload_table(path, "theta.csv", "theta"), + posterior = read_fit_payload_table(path, "posterior.csv", "posterior"), + predictions = read_fit_payload_table(path, "predictions.csv", "predictions"), + covariates = covariates, + transport = "outputs", + source = "output files", + path = path + ) +} + +load_fit_manifest_snapshot <- function(path) { + manifest_path <- file.path(path, pm_fit_manifest_file_name) + if (!file.exists(manifest_path)) { + return(NULL) + } + + decode_fit_result_envelope( + payload_json = paste(readLines(manifest_path, warn = FALSE), collapse = "\n"), + path = path + ) +} + +read_fit_payload_table <- function(path, file_name, table_name) { + file_path <- file.path(path, file_name) + if (!file.exists(file_path)) { + abort_pm_fit_payload_schema( + message = paste0("Missing required output file `", file_path, "`."), + class = "pmetrics_result_schema_missing_table_error", + table = table_name, + source = "output files", + path = path + ) + } + + readr::read_csv(file = file_path, show_col_types = FALSE) +} + +fit_payload_from_source <- function(data, path = ".") { + if (inherits(data, "PM_fit_payload")) { + return(data) + } + + manifest_payload <- load_fit_manifest_snapshot(path) + if (!is.null(manifest_payload)) { + return(manifest_payload) + } + + read_fit_payload_from_outputs(path) +} + +save_fit_payload_snapshot <- function(fit_payload, path) { + dir.create(path, recursive = TRUE, showWarnings = FALSE) + suppressWarnings(saveRDS(fit_payload, file = file.path(path, "fit_payload.rds"))) + invisible(TRUE) +} + +load_fit_payload_snapshot <- function(path) { + snapshot_path <- file.path(path, "fit_payload.rds") + if (!file.exists(snapshot_path)) { + return(NULL) + } + + fit_payload <- tryCatch(readRDS(snapshot_path), error = function(e) NULL) + if (!inherits(fit_payload, "PM_fit_payload")) { + return(NULL) + } + + fit_payload +} + +save_pm_result_snapshot <- function(out, path) { + dir.create(path, recursive = TRUE, showWarnings = FALSE) + PMout <- out + suppressWarnings(save(PMout, file = file.path(path, "PMout.Rdata"))) + invisible(TRUE) +} + +build_pm_result_from_normalized_payload <- function(fit_payload, data, model, path) { + out <- list( + pop = PM_pop$new(fit_payload, path = path), + post = PM_post$new(fit_payload, path = path), + final = PM_final$new(fit_payload, path = path), + cycle = PM_cycle$new(fit_payload, path = path), + op = PM_op$new(fit_payload, path = path), + cov = PM_cov$new(fit_payload, path = path), + data = data, + model = model, + errfile = NULL, + success = TRUE, + valid = NULL + ) + + save_fit_payload_snapshot(fit_payload, path) + save_pm_result_snapshot(out, path) + PM_result$new(out, path = path, quiet = TRUE) +} + +build_pm_result_from_fit_payload <- function(payload_json, data, model, path) { + fit_payload <- decode_fit_result_envelope(payload_json, path = path) + build_pm_result_from_normalized_payload(fit_payload, data = data, model = model, path = path) +} diff --git a/R/PM_live.R b/R/PM_live.R new file mode 100644 index 000000000..0be99e141 --- /dev/null +++ b/R/PM_live.R @@ -0,0 +1,150 @@ +# nolint start + +resolve_pmetrics_reports_run_app <- function() { + pmetrics_path <- tryCatch( + getNamespaceInfo(asNamespace("Pmetrics"), "path"), + error = function(e) "" + ) + sibling_reports_path <- normalizePath( + file.path(dirname(pmetrics_path), "Pmetricsreports"), + mustWork = FALSE + ) + + if (nzchar(sibling_reports_path) && + file.exists(file.path(sibling_reports_path, "DESCRIPTION")) && + requireNamespace("pkgload", quietly = TRUE)) { + loaded <- tryCatch( + { + pkgload::load_all(sibling_reports_path, quiet = TRUE, export_all = FALSE) + TRUE + }, + error = function(e) FALSE + ) + + if (isTRUE(loaded)) { + return(getExportedValue("PmetricsReports", "run_app")) + } + } + + if (!requireNamespace("PmetricsReports", quietly = TRUE)) { + return(NULL) + } + + getExportedValue("PmetricsReports", "run_app") +} + +start_live_report_session <- function(show = TRUE, timeout_ms = 10000L) { + run_app <- resolve_pmetrics_reports_run_app() + if (is.null(run_app)) { + return(NULL) + } + + live_session <- call_pm_bridge( + start_live_session(), + default_stage = "handoff", + default_code = "live_session_start_failed" + ) + + process <- tryCatch( + { + run_app( + res = NULL, + launch.browser = show, + background = TRUE, + live_session = live_session + ) + }, + error = function(e) { + try(close_live_session(live_session$session_id), silent = TRUE) + stop(e) + } + ) + + connected <- tryCatch( + call_pm_bridge( + wait_live_session_connected(live_session$session_id, as.integer(timeout_ms)), + default_stage = "handoff", + default_code = "live_session_wait_failed" + ), + error = function(e) FALSE + ) + + list( + session = live_session, + process = process, + connected = isTRUE(connected), + url = attr(process, "app_url", exact = TRUE), + close_grace_seconds = 1 + ) +} + +format_live_report_generated_at <- function(generated_at = Sys.time()) { + format( + as.POSIXct(generated_at, tz = "UTC"), + "%Y-%m-%dT%H:%M:%OS3Z", + tz = "UTC" + ) +} + +encode_live_report_result <- function(res) { + raw <- serialize(res, NULL, xdr = FALSE) + jsonlite::base64_enc(memCompress(raw, type = "gzip")) +} + +send_live_report_result <- function(live_report_session, res, generated_at = Sys.time()) { + if (is.null(live_report_session) || is.null(live_report_session$session$session_id)) { + return(invisible(FALSE)) + } + + call_pm_bridge( + publish_live_report_result( + session_id = live_report_session$session$session_id, + result_payload = encode_live_report_result(res), + report_generated_at = format_live_report_generated_at(generated_at) + ), + default_stage = "handoff", + default_code = "live_report_publish_failed" + ) + + invisible(TRUE) +} + +send_live_report_failure <- function(live_report_session, message) { + if (is.null(live_report_session) || is.null(live_report_session$session$session_id)) { + return(invisible(FALSE)) + } + + call_pm_bridge( + publish_live_report_failed( + session_id = live_report_session$session$session_id, + message = as.character(message[[1]]) + ), + default_stage = "handoff", + default_code = "live_report_failure_publish_failed" + ) + + invisible(TRUE) +} + +close_live_report_session <- function(live_report_session) { + if (is.null(live_report_session) || is.null(live_report_session$session$session_id)) { + return(invisible(FALSE)) + } + + grace <- suppressWarnings(as.numeric(live_report_session$close_grace_seconds[[1]])) + if (is.finite(grace) && grace > 0) { + Sys.sleep(grace) + } + + try( + call_pm_bridge( + close_live_session(live_report_session$session$session_id), + default_stage = "handoff", + default_code = "live_session_close_failed" + ), + silent = TRUE + ) + invisible(TRUE) +} + +# nolint end diff --git a/R/PM_model.R b/R/PM_model.R index a4b7b718c..21dda402b 100644 --- a/R/PM_model.R +++ b/R/PM_model.R @@ -566,7 +566,7 @@ PM_model <- R6::R6Class( eqn_list <- map_lgl(required_parameters, \(x){ any( - stringr::str_detect( + res <- tryCatch( stringr::str_remove_all(tolower(func_to_char(self$arg_list$eqn)), "\\s+"), # string paste0(x, "(?=(<-|=))") ) # pattern @@ -587,7 +587,7 @@ PM_model <- R6::R6Class( } if (!is.null(self$arg_fa)) { - lag_list <- map_lgl(required_parameters, \(x){ + fa_list <- map_lgl(required_parameters, \(x){ any( stringr::str_detect( stringr::str_remove_all(tolower(func_to_char(self$arg_list$fa)), "\\s+"), # string @@ -1316,7 +1316,7 @@ PM_model <- R6::R6Class( cli::cli_inform( c("i" = "The previous run from '{path_run}' was read.", " " = "Set {.arg overwrite} to {.val TRUE} to overwrite prior run in '{path_run}'.") ) - return(invisible(PM_load(file = normalizePath(file.path(path_run, "PMout.Rdata"), mustWork = FALSE)))) + return(invisible(PM_load(path = path, run = run))) } } @@ -1355,47 +1355,125 @@ PM_model <- R6::R6Class( if (intern) { ### CALL RUST out_path <- normalizePath(file.path(path_run, "outputs"), mustWork = FALSE) + dir.create(out_path, recursive = TRUE, showWarnings = FALSE) msg <- c(msg, "Run results were saved in folder '{.path {out_path}}'") + live_report_session <- NULL + live_report_started <- FALSE + live_report_requested <- identical(report, "app") + + if (live_report_requested) { + live_report_session <- tryCatch( + start_live_report_session(show = TRUE), + error = function(e) { + msg <<- c(msg, "Live reporting app could not be launched before the fit started.") + NULL + } + ) + + if (!is.null(live_report_session)) { + live_report_started <- TRUE + if (!isTRUE(live_report_session$connected)) { + msg <- c(msg, "Live reporting app did not connect before the first fit cycle.") + } + on.exit(close_live_report_session(live_report_session), add = TRUE) + } + } + + fit_params <- list( + ranges = ranges, # not important but needed for POSTPROB + algorithm = algorithm, + error_models = lapply(self$model_list$err, function(x) x$flatten()), + cache = cache, + progress = progress, + write_logs = write_logs, + stdout_logs = stdout_logs, + log_level = log_level, + idelta = idelta, + tad = tad, + max_cycles = cycles, # will be hardcoded in Rust to 0 for POSTPROB + prior = prior, # needs warning if missing and algorithm = POSTPROB + points = points, # only relevant for sobol prior + seed = seed + ) + + if (!is.null(live_report_session)) { + fit_params$live_session_id <- live_report_session$session$session_id + } + fit_call <- function() { fit( model_source = model_source, data = normalizePath(file.path(path_run, "inputs", "gendata.csv")), - params = list( - ranges = ranges, # not important but needed for POSTPROB - algorithm = algorithm, - error_models = lapply(self$model_list$err, function(x) x$flatten()), - cache = cache, - progress = progress, - write_logs = write_logs, - stdout_logs = stdout_logs, - log_level = log_level, - idelta = idelta, - tad = tad, - max_cycles = cycles, # will be hardcoded in Rust to 0 for POSTPROB - prior = prior, # needs warning if missing and algorithm = POSTPROB - points = points, # only relevant for sobol prior - seed = seed - ), + params = fit_params, output_path = out_path, kind = tolower(self$model_list$type), solver = self$model_list$solver ) } - rlang::try_fetch( + payload_json <- call_pm_bridge( if (!is.null(prior_dir) && identical(prior, "prior.csv")) { withr::with_dir(prior_dir, fit_call()) } else { fit_call() }, + default_stage = "runtime", + default_code = "fit_execution_failed" + ) + + res <- tryCatch( + build_pm_result_from_fit_payload( + payload_json = payload_json, + data = data, + model = self, + path = normalizePath(out_path) + ), error = function(e) { - cli::cli_warn("Unable to create {.cls PM_result} object", parent = e) - return(NULL) + handoff_message <- paste( + "Failed to build PM_result from fit payload:", + conditionMessage(e) + ) + if (!is.null(live_report_session)) { + try( + send_live_report_failure( + live_report_session, + handoff_message + ), + silent = TRUE + ) + } + + abort_pm_bridge_message( + message = handoff_message, + stage = "handoff", + code = "fit_result_build_failed", + details = list(path = normalizePath(out_path)), + parent = e + ) } ) - PM_parse(path = out_path) - res <- PM_load(path = normalizePath(out_path), file = "PMout.Rdata") - if (report != "none") { + if (live_report_started) { + tryCatch( + send_live_report_result(live_report_session, res), + error = function(e) { + msg <<- c(msg, "Live reporting app could not switch to the finished report.") + try( + send_live_report_failure( + live_report_session, + paste("Finished report handoff failed:", conditionMessage(e)) + ), + silent = TRUE + ) + invisible(NULL) + } + ) + + msg <- c(msg, "Live reporting app launched.") + if (!is.null(live_report_session$url) && nzchar(live_report_session$url)) { + msg <- c(msg, "Live reporting app URL: {live_report_session$url}") + } + msg <- c(msg, "If assigned to a variable, e.g. {.code run{run} <-}, results are available in {.code run{run}}.") + } else if (report != "none") { valid_report <- tryCatch( PM_report(res, path = normalizePath(out_path), template = report, quiet = TRUE), error = function(e) { @@ -1403,7 +1481,11 @@ PM_model <- R6::R6Class( } ) if (valid_report == 1) { - msg <- c(msg, "Reporting app launched.") + if (identical(report, "app")) { + msg <- c(msg, "Reporting app launched.") + } else { + msg <- c(msg, "Report opened.") + } # if(tolower(algorithm) == "postprob") {this_alg <- "map"} else {this_alg <- "fit"} msg <- c(msg, "If assigned to a variable, e.g. {.code run{run} <-}, results are available in {.code run{run}}.") } else { @@ -1519,12 +1601,16 @@ PM_model <- R6::R6Class( rewrite_runtime_route_labels(temp_csv, self) self$compile(quiet = quiet) - sim <- simulate_all( - temp_csv, - self$dsl(), - theta, - kind = tolower(self$model_list$type), - solver = self$model_list$solver + sim <- call_pm_bridge( + simulate_all( + temp_csv, + self$dsl(), + theta, + kind = tolower(self$model_list$type), + solver = self$model_list$solver + ), + default_stage = "runtime", + default_code = "simulation_failed" ) return(sim) @@ -1581,6 +1667,7 @@ PM_model <- R6::R6Class( writeLines(numbered_lines) } + validation_stage <- "validated" ok <- tryCatch( { validate_model_source( @@ -1591,7 +1678,9 @@ PM_model <- R6::R6Class( TRUE }, error = function(e) { - diagnostic <<- conditionMessage(e) + bridge_error <- parse_pm_bridge_error(conditionMessage(e)) + diagnostic <<- bridge_error$message + validation_stage <<- if (is.null(bridge_error$stage)) "runtime" else bridge_error$stage FALSE } ) @@ -1607,7 +1696,7 @@ PM_model <- R6::R6Class( invisible(list( ok = ok, - stage = if (ok) "validated" else "runtime", + stage = if (ok) "validated" else validation_stage, dsl = model_source, numbered_dsl = numbered_dsl, diagnostic = if (ok) NULL else diagnostic @@ -1621,19 +1710,14 @@ PM_model <- R6::R6Class( #' @param quiet Logical, if TRUE, suppresses messages during compilation. compile = function(quiet = FALSE) { if (!quiet) cli::cli_inform(c("i" = "Compiling model...")) - tryCatch( - { - validate_model_source( - model_source = self$dsl(), - kind = tolower(self$model_list$type), - solver = self$model_list$solver - ) - }, - error = function(e) { - cli::cli_abort( - c("x" = "Model compilation failed: {e$message}", "i" = "Please check the model file and try again.") - ) - } + call_pm_bridge( + validate_model_source( + model_source = self$dsl(), + kind = tolower(self$model_list$type), + solver = self$model_list$solver + ), + default_stage = "compile", + default_code = "model_compile_failed" ) return(invisible(self)) diff --git a/R/PM_op.R b/R/PM_op.R index 52921ebd1..800f168a4 100755 --- a/R/PM_op.R +++ b/R/PM_op.R @@ -128,52 +128,19 @@ PM_op <- R6::R6Class( ), # end public private = list( make = function(data, path) { - if (file.exists(file.path(path, "predictions.csv"))) { - op_raw <- readr::read_csv( - file = file.path(path, "predictions.csv"), - col_types = list( - time = readr::col_double(), - outeq = readr::col_integer(), - block = readr::col_integer(), - obs = readr::col_double(), - cens = readr::col_character(), - pop_mean = readr::col_double(), - pop_median = readr::col_double(), - post_mean = readr::col_double(), - post_median = readr::col_double() - ), show_col_types = FALSE - ) |> filter(!is.na(obs)) - - if (!"cens" %in% names(op_raw)) { - op_raw <- op_raw |> mutate(cens = "none") # if cens column missing, assume all observed - } - } else if (inherits(data, "PM_op") & !is.null(data$data)) { # file not there, and already PM_op + config <- NULL + if (inherits(data, "PM_op") & !is.null(data$data)) { if (!"cens" %in% names(data$data)) { data$data <- data$data |> mutate(cens = "none") # if cens column missing, assume all observed } class(data$data) <- c("PM_op_data", "data.frame") return(data$data) - } else { - cli::cli_warn(c( - "!" = "Unable to generate obs-pred information.", - "i" = "{.file {file.path(path, 'predictions.csv')}} does not exist, and result does not have valid {.code PM_op} object ." - )) - return(NULL) } + fit_payload <- fit_payload_from_source(data, path) + op_raw <- tibble::as_tibble(fit_payload$predictions) |> filter(!is.na(obs)) + config <- fit_payload$config - if (file.exists(file.path(path, "settings.json"))) { - config <- jsonlite::fromJSON(file.path(path, "settings.json")) - } else if (inherits(data, "PM_op")) { # file not there, and already PM_op - class(data$data) <- c("PM_op_data", "data.frame") - return(data$data) - } else { - cli::cli_warn(c( - "!" = "Unable to generate obs-pred information.", - "i" = "{.file {file.path(path, 'settings.json')}} does not exist, and result does not have valid {.code PM_op} object." - )) - return(NULL) - } poly <- decode_error_model_rows(config$errormodels$models, op_raw$outeq) calc_obs_sd <- function(outeq, obs) { diff --git a/R/PM_parse.R b/R/PM_parse.R index 86ebeab1b..aa33b83e9 100755 --- a/R/PM_parse.R +++ b/R/PM_parse.R @@ -1,5 +1,7 @@ # PM_parse ---------------------------------------------------------------- +# nolint start + #' @title Parse Pmetrics output #' @description @@ -22,73 +24,35 @@ #' @export #' @keywords internal -PM_parse <- function(path = ".", fit = "fit.rds", write = TRUE) { - if (is.character(fit) && file.exists(file.path(path, "../inputs", fit))) { - # fit is a character string pointing to a file, load it - fit_object <- readRDS(file.path(path, "../inputs", fit)) - } else { - # fit does not meet any of the above conditions, set to NULL - fit_object <- NULL +load_pm_parse_fit_object <- function(path, fit = "fit.rds") { + if (!is.character(fit) || length(fit) != 1) { + return(NULL) } - - if (!dir.exists(path)) { - cli::cli_abort(c("x" = "The directory {.path {path}} does not exist.")) + fit_path <- normalizePath(file.path(path, "..", "inputs", fit), mustWork = FALSE) + if (!file.exists(fit_path)) { + return(NULL) } - # assumes predictions.csv and settings.json are in wd - op <- rlang::try_fetch(PM_op$new(path = path), - error = function(e) { - cli::cli_warn("Unable to create {.cls PM_op} object", parent = e) - return(NULL) - } - ) - - # assumes theta.csv and posterior.csv are in wd - final <- rlang::try_fetch(PM_final$new(path = path), - error = function(e) { - cli::cli_warn("Unable to create {.cls PM_final} object", parent = e) - return(NULL) - } - ) + tryCatch(readRDS(fit_path), error = function(e) NULL) +} - # assumes iterations.csv and settings.json are in wd - cycle <- rlang::try_fetch(PM_cycle$new(path = path), - error = function(e) { - cli::cli_warn("Unable to create {.cls PM_cycle} object", parent = e) - return(NULL) - } - ) +PM_parse <- function(path = ".", fit = "fit.rds", write = TRUE) { + fit_object <- load_pm_parse_fit_object(path, fit = fit) - # assumes predictions.csv is in wd - pop <- rlang::try_fetch(PM_pop$new(path = path), - error = function(e) { - cli::cli_warn("Unable to create {.cls PM_pop} object", parent = e) - return(NULL) - } - ) - # assumes predictions.csv is in wd - post <- rlang::try_fetch(PM_post$new(path = path), - error = function(e) { - cli::cli_warn("Unable to create {.cls PM_post} object", parent = e) - return(NULL) - } - ) + if (!dir.exists(path)) { + cli::cli_abort(c("x" = "The directory {.path {path}} does not exist.")) + } - cov <- rlang::try_fetch(PM_cov$new(path = path), - error = function(e) { - cli::cli_warn("Unable to create {.cls PM_cov} object", parent = e) - return(NULL) - } - ) + fit_payload <- read_fit_payload_from_outputs(path) - config <- rlang::try_fetch(jsonlite::fromJSON(suppressWarnings(readLines(file.path(path, "settings.json"), warn = FALSE))), - error = function(e) { - cli::cli_warn(c("!" = "Unable to read {.file {file.path(path, 'settings.json')}}")) - return(NULL) - } - ) + op <- PM_op$new(fit_payload, path = path) + final <- PM_final$new(fit_payload, path = path) + cycle <- PM_cycle$new(fit_payload, path = path) + pop <- PM_pop$new(fit_payload, path = path) + post <- PM_post$new(fit_payload, path = path) + cov <- PM_cov$new(fit_payload, path = path) core <- list( data = fit_object$data, @@ -100,7 +64,7 @@ PM_parse <- function(path = ".", fit = "fit.rds", write = TRUE) { cycle = cycle, final = final, converge = cycle$data$converged, - config = config, + config = fit_payload$config, sys = { info <- as.list(Sys.info()) info |> @@ -120,3 +84,5 @@ PM_parse <- function(path = ".", fit = "fit.rds", write = TRUE) { return(invisible(core)) } + +# nolint end diff --git a/R/PM_pop.R b/R/PM_pop.R index aa88883e7..1798e9a0f 100755 --- a/R/PM_pop.R +++ b/R/PM_pop.R @@ -91,32 +91,14 @@ PM_pop <- R6::R6Class( ), # end public private = list( make = function(data, path) { - if (file.exists(file.path(path, "predictions.csv"))) { - op_raw <- readr::read_csv( - file = file.path(path, "predictions.csv"), - col_types = list( - time = readr::col_double(), - outeq = readr::col_integer(), - block = readr::col_integer(), - obs = readr::col_double(), - cens = readr::col_character(), - pop_mean = readr::col_double(), - pop_median = readr::col_double(), - post_mean = readr::col_double(), - post_median = readr::col_double() - ), show_col_types = FALSE - ) - } else if (inherits(data, "PM_pop") & !is.null(data$data)) { # file not there, and already PM_pop + if (inherits(data, "PM_pop") & !is.null(data$data)) { # file not there, and already PM_pop class(data$data) <- c("PM_pop_data", "data.frame") return(data$data) - } else { - cli::cli_warn(c( - "!" = "Unable to generate pop pred information.", - "i" = "{.file {file.path(path, 'predictions.csv')}} does not exist, and result does not have valid {.code PM_pop} object." - )) - return(NULL) } + fit_payload <- fit_payload_from_source(data, path) + op_raw <- tibble::as_tibble(fit_payload$predictions) + if (is.null(op_raw)) { return(NA) } diff --git a/R/PM_post.R b/R/PM_post.R index b4f2b11c2..ed21b414b 100755 --- a/R/PM_post.R +++ b/R/PM_post.R @@ -92,32 +92,14 @@ PM_post <- R6::R6Class( ), # end public private = list( make = function(data, path) { - if (file.exists(file.path(path, "predictions.csv"))) { - op_raw <- readr::read_csv( - file = file.path(path, "predictions.csv"), - col_types = list( - time = readr::col_double(), - outeq = readr::col_integer(), - block = readr::col_integer(), - obs = readr::col_double(), - cens = readr::col_character(), - pop_mean = readr::col_double(), - pop_median = readr::col_double(), - post_mean = readr::col_double(), - post_median = readr::col_double() - ), show_col_types = FALSE - ) - } else if (inherits(data, "PM_post") & !is.null(data$data)) { # file not there, and already PM_post + if (inherits(data, "PM_post") & !is.null(data$data)) { # file not there, and already PM_post class(data$data) <- c("PM_post_data", "data.frame") return(data$data) - } else { - cli::cli_warn(c( - "!" = "Unable to generate post pred information.", - "i" = "{.file {file.path(path, 'predictions.csv')}} does not exist, and result does not have valid {.code PM_post} object." - )) - return(NULL) } + fit_payload <- fit_payload_from_source(data, path) + op_raw <- tibble::as_tibble(fit_payload$predictions) + if (is.null(op_raw)) { return(NA) } diff --git a/R/PM_report.R b/R/PM_report.R index 4f9fcc407..b36c89bf4 100755 --- a/R/PM_report.R +++ b/R/PM_report.R @@ -2,16 +2,17 @@ #' @description #' `r lifecycle::badge("stable")` #' -#' Launches the Pmetrics reporting app for a completed run +#' Launches the Pmetrics reporting app or generates an HTML report for a completed run #' #' @param x A [PM_result] object obtained from [PM_load]. -#' @param template Deprecated for app mode. Used only for HTML fallback report -#' generation when app launch fails. -#' @param path Deprecated for app mode. Used only for HTML fallback report -#' generation when app launch fails. +#' @param template If missing, the current report mode from [getPMoptions] is used. +#' Use `"app"` to launch the PmetricsReports app or `"plotly"`, `"ggplot"`, +#' or `"ggplot_rust"` to generate the legacy HTML report. +#' @param path Output folder for the generated HTML report. Ignored for app mode +#' unless the app launch fails and HTML fallback is used. #' @param show Controls whether the Shiny app should be opened automatically, defaults to `TRUE`. #' @param quiet Retained for compatibility. -#' @return Launches the Pmetrics reporting app. +#' @return Launches the selected report mode. #' @author Markus Hovd, Julian Otalvaro, and Michael Neely #' @seealso [PM_load] #' @export @@ -20,20 +21,73 @@ PM_report <- function(x, template, path, show = TRUE, quiet = TRUE) { template_missing <- missing(template) path_missing <- missing(path) + resolve_run_app <- function() { + pmetrics_path <- tryCatch( + getNamespaceInfo(asNamespace("Pmetrics"), "path"), + error = function(e) "" + ) + sibling_reports_path <- normalizePath( + file.path(dirname(pmetrics_path), "Pmetricsreports"), + mustWork = FALSE + ) + + if (nzchar(sibling_reports_path) && + file.exists(file.path(sibling_reports_path, "DESCRIPTION")) && + requireNamespace("pkgload", quietly = TRUE)) { + loaded <- tryCatch( + { + pkgload::load_all(sibling_reports_path, quiet = TRUE, export_all = FALSE) + TRUE + }, + error = function(e) FALSE + ) + + if (isTRUE(loaded)) { + return(getExportedValue("PmetricsReports", "run_app")) + } + } + + if (!requireNamespace("PmetricsReports", quietly = TRUE)) { + return(NULL) + } + + getExportedValue("PmetricsReports", "run_app") + } + + resolve_report_mode <- function() { + mode <- if (template_missing) { + getPMoptions("report_template", warn = FALSE, quiet = TRUE) + } else { + template + } + + if (is.null(mode) || identical(mode, -1)) { + return("app") + } + + mode <- as.character(mode[[1]]) + if (is.na(mode) || !nzchar(mode)) { + return("app") + } + + mode + } + + report_mode <- resolve_report_mode() + if (!is(x, "PM_result")) { cli::cli_abort(c("x" = "This function expects a valid PM_result object from PM_load.")) } - if (!template_missing && identical(template, "none")) { + if (identical(report_mode, "none")) { return(invisible(0)) } - if (is.null(x$final$data) & is.null(x$op$data) & is.null(x$cycle$data)) { + if (is.null(x$final$data) && is.null(x$op$data) && is.null(x$cycle$data)) { return(invisible(-1)) # no data found } - render_html_fallback <- function() { - fallback_template <- if (template_missing) getPMoptions("report_template") else template + render_html_fallback <- function(fallback_template = report_mode) { if (identical(fallback_template, "none")) { return(invisible(0)) } @@ -72,7 +126,7 @@ PM_report <- function(x, template, path, show = TRUE, quiet = TRUE) { dir.create(out_path, recursive = TRUE, showWarnings = FALSE) } - + rmarkdown::render( input = template_file, output_file = file.path(out_path, "report.html"), @@ -91,7 +145,12 @@ PM_report <- function(x, template, path, show = TRUE, quiet = TRUE) { invisible(-1) } - if (!requireNamespace("PmetricsReports", quietly = TRUE)) { + if (!identical(report_mode, "app")) { + return(render_html_fallback(report_mode)) + } + + run_app <- resolve_run_app() + if (is.null(run_app)) { cli::cli_warn(c( "!" = "The {.pkg PmetricsReports} package is not available.", "i" = "Falling back to legacy HTML report generation.", @@ -102,8 +161,11 @@ PM_report <- function(x, template, path, show = TRUE, quiet = TRUE) { tryCatch( { - run_app <- getExportedValue("PmetricsReports", "run_app") - run_app(res = x, launch.browser = show) + app_process <- run_app(res = x, launch.browser = show) + app_url <- attr(app_process, "app_url", exact = TRUE) + if (!is.null(app_url) && nzchar(app_url)) { + cli::cli_inform(c("i" = "Reporting app URL: {app_url}")) + } invisible(1) }, error = function(e) { diff --git a/R/PM_result.R b/R/PM_result.R index 3b8ea3640..b21095c99 100755 --- a/R/PM_result.R +++ b/R/PM_result.R @@ -310,6 +310,74 @@ PM_result$load <- function(...) { lifecycle::deprecate_warn("2.1.0", "PM_result$load()", details = "Please use PM_load() instead. ?PM_load for details.") } +# nolint start +load_pm_result_snapshot <- function(found) { + output2List <- function(Out) { + result <- list() + for (i in seq_along(Out)) { + aux_list <- list(Out[[i]]) + names(aux_list) <- names(Out)[i] + result <- append(result, aux_list) + } + + result + } + + result <- output2List(Out = get(load(found))) + PM_result$new(result, path = dirname(found), quiet = TRUE) +} + +rebuild_pm_result_from_outputs <- function(output_path) { + fit_object <- load_pm_parse_fit_object(output_path, fit = "fit.rds") + fit_payload <- load_fit_payload_snapshot(output_path) + + if (!is.null(fit_payload)) { + if (is.null(fit_object$data) || is.null(fit_object$model)) { + cli::cli_abort(c( + "x" = "No Pmetrics output file found in {.path {output_path}}.", + "i" = "The standard run inputs needed to rebuild this result are missing." + )) + } + + return(build_pm_result_from_normalized_payload( + fit_payload, + data = fit_object$data, + model = fit_object$model, + path = output_path + )) + } + + fit_manifest <- load_fit_manifest_snapshot(output_path) + + if (!is.null(fit_manifest)) { + if (is.null(fit_object$data) || is.null(fit_object$model)) { + cli::cli_abort(c( + "x" = "No Pmetrics output file found in {.path {output_path}}.", + "i" = "The standard run inputs needed to rebuild this result are missing." + )) + } + + return(build_pm_result_from_normalized_payload( + fit_manifest, + data = fit_object$data, + model = fit_object$model, + path = output_path + )) + } + + parsed <- PM_parse(path = output_path, write = FALSE) + + if (is.null(parsed$data) || is.null(parsed$model)) { + cli::cli_abort(c( + "x" = "No Pmetrics output file found in {.path {output_path}}.", + "i" = "The standard run inputs needed to rebuild this result are missing." + )) + } + + PM_result$new(parsed, path = output_path, quiet = TRUE) +} +# nolint end + # LOAD -------------------------------------------------------------------- #' @title Load Pmetrics NPAG or IT2B output @@ -364,20 +432,6 @@ PM_result$load <- function(...) { PM_load <- function(run, path = ".", file = "PMout.Rdata") { - # internal function - output2List <- function(Out) { - result <- list() - for (i in seq_along(Out)) { - aux_list <- list(Out[[i]]) - names(aux_list) <- names(Out)[i] - result <- append(result, aux_list) - } - - return(result) - } - - found <- "" # initialize - if (!missing(run)) { filepath <- file.path(path, run, "outputs", file) } else { @@ -385,17 +439,32 @@ PM_load <- function(run, path = ".", file = "PMout.Rdata") { } if (file.exists(filepath)) { - found <- filepath + return(load_pm_result_snapshot(filepath)) } - if (found != "") { - result <- output2List(Out = get(load(found))) - rebuild <- PM_result$new(result, path = dirname(found), quiet = TRUE) + rebuild_error <- NULL + if (identical(basename(filepath), "PMout.Rdata") && dir.exists(dirname(filepath))) { + rebuilt <- tryCatch( + rebuild_pm_result_from_outputs(dirname(filepath)), + error = function(e) { + rebuild_error <<- e + NULL + } + ) - return(rebuild) - } else { - cli::cli_abort(c("x" = "No Pmetrics output file found in {.path {path}}.")) + if (!is.null(rebuilt)) { + return(rebuilt) + } } + + if (!is.null(rebuild_error)) { + cli::cli_abort(c( + "x" = "No Pmetrics output file found in {.path {path}}.", + "i" = "Rebuilding from standard run outputs failed: {conditionMessage(rebuild_error)}" + )) + } + + cli::cli_abort(c("x" = "No Pmetrics output file found in {.path {path}}.")) } @@ -468,3 +537,45 @@ update <- function(res, found) { return(res) } + +# Export the result to standard output files without relying on legacy fit-time writes. +PM_result$set("public", "export", function(path = ".") { + path <- normalizePath(path, mustWork = FALSE) + dir.create(path, recursive = TRUE, showWarnings = FALSE) + + if (!is.null(self$cycle$data)) { + write.csv(self$cycle$data, file.path(path, "iterations.csv"), row.names = FALSE) + } + + if (!is.null(self$final$data)) { + write.csv(self$final$data$theta, file.path(path, "theta.csv"), row.names = FALSE) + } + + if (!is.null(self$post$data)) { + write.csv(self$post$data$posterior, file.path(path, "posterior.csv"), row.names = FALSE) + } + + if (!is.null(self$pop$data)) { + write.csv(self$pop$data$pop, file.path(path, "predictions.csv"), row.names = FALSE) + } + + if (!is.null(self$cov$data)) { + write.csv(self$cov$data$covariates, file.path(path, "covariates.csv"), row.names = FALSE) + } + + if (!is.null(self$model) && !is.null(self$data)) { + settings <- list( + algorithm = if (!is.null(self$model$model_list$algorithm)) self$model$model_list$algorithm else "NPAG", + errormodels = if (!is.null(self$model$model_list$err)) { + lapply(self$model$model_list$err, function(e) list(type = e$type, coeff = e$coeff)) + }, + parameters = if (!is.null(self$model$model_list$pri)) { + lapply(self$model$model_list$pri, function(p) list(min = p$min, max = p$max)) + } + ) + writeLines(jsonlite::toJSON(settings, auto_unbox = TRUE), file.path(path, "settings.json")) + } + + invisible(TRUE) +}) + diff --git a/R/extendr-wrappers.R b/R/extendr-wrappers.R index 9909d26b8..b46696f9a 100755 --- a/R/extendr-wrappers.R +++ b/R/extendr-wrappers.R @@ -72,5 +72,43 @@ model_parameters <- function(model_source, kind) .Call(wrap__model_parameters, m #' @export setup_logs <- function() .Call(wrap__setup_logs) +#' Start a local live reporting session. +#' @return Live session connection info. +#' @keywords internal +#' @export +start_live_session <- function() .Call(wrap__start_live_session) + +#' Wait for a live reporting client to connect. +#' @param session_id Live session identifier. +#' @param timeout_ms Maximum wait in milliseconds. +#' @return TRUE when a client connects before the timeout. +#' @keywords internal +#' @export +wait_live_session_connected <- function(session_id, timeout_ms) .Call(wrap__wait_live_session_connected, session_id, timeout_ms) + +#' Close a local live reporting session. +#' @param session_id Live session identifier. +#' @return NULL, invisibly. +#' @keywords internal +#' @export +close_live_session <- function(session_id) .Call(wrap__close_live_session, session_id) + +#' Publish a finished report payload to a live reporting session. +#' @param session_id Live session identifier. +#' @param result_payload Serialized report payload. +#' @param report_generated_at Report timestamp. +#' @return NULL, invisibly. +#' @keywords internal +#' @export +publish_live_report_result <- function(session_id, result_payload, report_generated_at) .Call(wrap__publish_live_report_result, session_id, result_payload, report_generated_at) + +#' Publish a finished report handoff failure to a live reporting session. +#' @param session_id Live session identifier. +#' @param message Failure message. +#' @return NULL, invisibly. +#' @keywords internal +#' @export +publish_live_report_failed <- function(session_id, message) .Call(wrap__publish_live_report_failed, session_id, message) + # nolint end diff --git a/R/globalVariables.R b/R/globalVariables.R index e3761d1ed..145f27be6 100755 --- a/R/globalVariables.R +++ b/R/globalVariables.R @@ -28,7 +28,9 @@ utils::globalVariables( str_split sym symbol theme_void time1 time2 title updateCheckboxInput val xend xlim yend ylim wt x y base_color base_symbol cov_group outeq_group req . - .target_first agecat" |> + .target_first agecat build_fit_payload_config build_pm_result_from_normalized_payload + load_fit_manifest_snapshot load_fit_payload_snapshot load_pm_parse_fit_object + read_fit_payload_from_outputs" |> stringr::str_replace_all("\n\\s+", " ") |> stringr::str_split(" ") |> unlist() diff --git a/inst/Examples/Rscript/bimodal_ke.R b/inst/Examples/Rscript/bimodal_ke.R index a1c1ae98d..554745c2d 100644 --- a/inst/Examples/Rscript/bimodal_ke.R +++ b/inst/Examples/Rscript/bimodal_ke.R @@ -38,7 +38,8 @@ fit <- model$fit( points = 2028, seed = 22, idelta = 0.01, - overwrite = TRUE + overwrite = TRUE, + report = "app" ) cat("Status:", fit$cycle$data$status, "\n") diff --git a/man/PM_load.Rd b/man/PM_load.Rd index 7af4b9180..bc09a0a89 100755 --- a/man/PM_load.Rd +++ b/man/PM_load.Rd @@ -14,7 +14,9 @@ PM_load(run, path = ".", file = "PMout.Rdata") \item{file}{Default is "PMout.Rdata", which is created after a Pmetrics run, but it could also be the name of an .Rdata file created by running the -\verb{$save} method for a \link{PM_result} object.} +\verb{$save} method for a \link{PM_result} object. If the default snapshot is +missing, \code{PM_load()} will rebuild the result from the standard run outputs and +the saved \code{fit.rds} input snapshot when they are available.} } \value{ An R6 \link{PM_result}. @@ -31,6 +33,8 @@ A combination of \code{run}, \code{path}, and \code{file} are used to locate the \item If \code{run} is missing, the results are assumed to be in the \code{path} folder. \item The \code{file} name is the name of the Rdata file containing the results. Default is "PMout.Rdata", which is created by Pmetrics after a run. +If that default snapshot is missing, \code{PM_load()} rebuilds the result from the +standard run outputs and the saved \code{fit.rds} input snapshot when possible. \item If both \code{run} and \code{path} are missing, the current working directory is used for \code{path}. \item If both \code{run} and \code{file} are missing, the current working directory is used diff --git a/man/PM_model.Rd b/man/PM_model.Rd index 30ecbe845..76024b546 100755 --- a/man/PM_model.Rd +++ b/man/PM_model.Rd @@ -592,15 +592,14 @@ Default is \code{TRUE}.} \item{\code{progress}}{Logical. Whether to print runtime likelihood-matrix progress during fitting. Default is \code{TRUE}.} -\item{\code{write_logs}}{Logical. Whether to write fit tracing logs to -\code{outputs/log.txt}. Default is \code{FALSE}.} +\item{\code{write_logs}}{Logical. Whether to write fit tracing logs to \code{outputs/log.txt}. +Default is \code{FALSE}.} \item{\code{stdout_logs}}{Logical. Whether to print fit tracing logs in the R console. Default is \code{TRUE}.} -\item{\code{log_level}}{Character scalar. Fit tracing level. One of \code{"ERROR"}, -\code{"WARN"}, \code{"INFO"}, \code{"DEBUG"}, or \code{"TRACE"}. Default is -\code{"INFO"}.} +\item{\code{log_level}}{Character scalar. Fit tracing level. One of \code{"ERROR"}, \code{"WARN"}, +\code{"INFO"}, \code{"DEBUG"}, or \code{"TRACE"}. Default is \code{"INFO"}.} \item{\code{idelta}}{How often to generate posterior predictions in units of time. Default is 0.1, which means a prediction is generated every 0.1 hours (6 minutes) diff --git a/man/PM_report.Rd b/man/PM_report.Rd index dfa1c0532..cdbc4437c 100755 --- a/man/PM_report.Rd +++ b/man/PM_report.Rd @@ -9,23 +9,24 @@ PM_report(x, template, path, show = TRUE, quiet = TRUE) \arguments{ \item{x}{A \link{PM_result} object obtained from \link{PM_load}.} -\item{template}{Deprecated for app mode. Used only for HTML fallback report -generation when app launch fails.} +\item{template}{If missing, the current report mode from \link{getPMoptions} is used. +Use \code{"app"} to launch the PmetricsReports app or \code{"plotly"}, \code{"ggplot"}, +or \code{"ggplot_rust"} to generate the legacy HTML report.} -\item{path}{Deprecated for app mode. Used only for HTML fallback report -generation when app launch fails.} +\item{path}{Output folder for the generated HTML report. Ignored for app mode +unless the app launch fails and HTML fallback is used.} \item{show}{Controls whether the Shiny app should be opened automatically, defaults to \code{TRUE}.} \item{quiet}{Retained for compatibility.} } \value{ -Launches the Pmetrics reporting app. +Launches the selected report mode. } \description{ \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} -Launches the Pmetrics reporting app for a completed run +Launches the Pmetrics reporting app or generates an HTML report for a completed run } \seealso{ \link{PM_load} diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 7d5d15a82..bbe7d7c97 100755 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -15,6 +15,10 @@ rayon = "1.10.0" anyhow = "1.0.97" tracing = "0.1.41" tracing-subscriber = "0.3.20" +serde = { version = "1.0.219", features = ["derive"] } +serde_json = "1.0.140" +csv = "1.3.1" +sha2 = "0.10.8" [dev-dependencies] extendr-engine = "=0.9.0" diff --git a/src/rust/src/bridge_errors.rs b/src/rust/src/bridge_errors.rs new file mode 100644 index 000000000..d230d837b --- /dev/null +++ b/src/rust/src/bridge_errors.rs @@ -0,0 +1,163 @@ +use extendr_api::Error as RExtendrError; +use serde::Serialize; +use serde_json::{Map, Value}; +use std::fmt; + +pub(crate) const BRIDGE_ERROR_PREFIX: &str = "PMETRICS_BRIDGE_ERROR="; +const BRIDGE_ERROR_SCHEMA_VERSION: u32 = 1; + +pub(crate) type BridgeResult = Result; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] +#[serde(rename_all = "snake_case")] +pub(crate) enum BridgeStage { + Compile, + Data, + Settings, + Runtime, + Handoff, +} + +#[derive(Debug, Clone, Serialize)] +struct BridgeErrorEnvelope { + kind: &'static str, + schema_version: u32, + stage: BridgeStage, + code: String, + message: String, + diagnostic: Option, + details: Map, +} + +#[derive(Debug, Clone)] +pub(crate) struct BridgeError { + envelope: BridgeErrorEnvelope, +} + +impl BridgeError { + pub(crate) fn new( + stage: BridgeStage, + code: impl Into, + message: impl Into, + ) -> Self { + Self { + envelope: BridgeErrorEnvelope { + kind: "error", + schema_version: BRIDGE_ERROR_SCHEMA_VERSION, + stage, + code: code.into(), + message: message.into(), + diagnostic: None, + details: Map::new(), + }, + } + } + + pub(crate) fn from_anyhow( + stage: BridgeStage, + code: impl Into, + error: impl Into, + ) -> Self { + let error = error.into(); + Self::new(stage, code, error.to_string()) + } + + pub(crate) fn with_diagnostic(mut self, diagnostic: impl Into) -> Self { + self.envelope.diagnostic = Some(diagnostic.into()); + self + } + + pub(crate) fn with_detail(mut self, key: &str, value: impl Serialize) -> Self { + if let Ok(value) = serde_json::to_value(value) { + self.envelope.details.insert(key.to_string(), value); + } + self + } + + pub(crate) fn stage(&self) -> BridgeStage { + self.envelope.stage + } + + pub(crate) fn code(&self) -> &str { + &self.envelope.code + } + + pub(crate) fn message(&self) -> &str { + &self.envelope.message + } + + pub(crate) fn compatibility_string(&self) -> String { + let mut rendered = self.envelope.message.clone(); + if let Some(diagnostic) = &self.envelope.diagnostic { + rendered.push('\n'); + rendered.push_str(diagnostic); + } + + let envelope_json = serde_json::to_string(&self.envelope).unwrap_or_else(|_| { + format!( + "{{\"kind\":\"error\",\"schema_version\":{},\"stage\":\"handoff\",\"code\":\"bridge_error_serialize_failed\",\"message\":\"Failed to serialize bridge error envelope\",\"diagnostic\":null,\"details\":{{}}}}", + BRIDGE_ERROR_SCHEMA_VERSION + ) + }); + + rendered.push_str("\n\n"); + rendered.push_str(BRIDGE_ERROR_PREFIX); + rendered.push_str(&envelope_json); + rendered + } +} + +impl fmt::Display for BridgeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.envelope.message)?; + if let Some(diagnostic) = &self.envelope.diagnostic { + write!(f, "\n{}", diagnostic)?; + } + Ok(()) + } +} + +impl std::error::Error for BridgeError {} + +impl From for RExtendrError { + fn from(error: BridgeError) -> Self { + RExtendrError::Other(error.compatibility_string()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn bridge_error_serializes_stage_and_details() { + let error = BridgeError::new(BridgeStage::Runtime, "fit_failed", "fit failed") + .with_detail("subject_count", 12) + .with_detail("algorithm", "npag"); + + let rendered = error.compatibility_string(); + let payload = rendered + .split_once(BRIDGE_ERROR_PREFIX) + .expect("bridge error prefix should be present") + .1; + let json: Value = serde_json::from_str(payload).expect("bridge envelope json should parse"); + + assert_eq!(json["kind"], "error"); + assert_eq!(json["schema_version"], BRIDGE_ERROR_SCHEMA_VERSION); + assert_eq!(json["stage"], "runtime"); + assert_eq!(json["code"], "fit_failed"); + assert_eq!(json["details"]["subject_count"], 12); + assert_eq!(json["details"]["algorithm"], "npag"); + } + + #[test] + fn bridge_error_compatibility_string_keeps_human_message_first() { + let error = BridgeError::new(BridgeStage::Compile, "compile_failed", "compile failed") + .with_diagnostic("line 1: unexpected token"); + + let rendered = error.compatibility_string(); + + assert!(rendered.starts_with("compile failed\nline 1: unexpected token")); + assert!(rendered.contains(BRIDGE_ERROR_PREFIX)); + } +} \ No newline at end of file diff --git a/src/rust/src/executor.rs b/src/rust/src/executor.rs index 789b80f0b..6d7ed7305 100755 --- a/src/rust/src/executor.rs +++ b/src/rust/src/executor.rs @@ -1,14 +1,23 @@ -use crate::settings::settings; +use crate::bridge_errors::{BridgeError, BridgeResult, BridgeStage}; +use crate::fit_payload::{self, FitResultEnvelope}; +use crate::live; +use crate::settings::{fit_payload_config, live_session_id, runtime_interval, settings}; use extendr_api::List; use pmcore::prelude::{simulator::SubjectPredictions, Predictions, *}; use crate::simulation::SimulationRow; -pub(crate) fn model_parameters(equation: &E) -> Result> { +pub(crate) fn model_parameters(equation: &E) -> BridgeResult> { let metadata = equation .equation_metadata() - .ok_or_else(|| anyhow::anyhow!("runtime model metadata is required"))?; + .ok_or_else(|| { + BridgeError::new( + BridgeStage::Runtime, + "runtime_metadata_missing", + "runtime model metadata is required", + ) + })?; Ok(metadata .parameters() @@ -22,24 +31,45 @@ pub(crate) fn simulate( subject: &Subject, parameters: &Parameters, spp_index: usize, -) -> Result> +) -> BridgeResult> where E: Equation + EquationMetadataSource, { let metadata = equation .equation_metadata() - .ok_or_else(|| anyhow::anyhow!("runtime model metadata is required"))?; + .ok_or_else(|| { + BridgeError::new( + BridgeStage::Runtime, + "runtime_metadata_missing", + "runtime model metadata is required", + ) + })?; if metadata.parameters().len() != parameters.as_slice().len() { - return Err(anyhow::anyhow!( - "Support point has {} values but model expects {} parameters", - parameters.as_slice().len(), - metadata.parameters().len() - )); + return Err( + BridgeError::new( + BridgeStage::Runtime, + "support_point_parameter_count_mismatch", + format!( + "Support point has {} values but model expects {} parameters", + parameters.as_slice().len(), + metadata.parameters().len() + ), + ) + .with_detail("provided_parameter_count", parameters.as_slice().len()) + .with_detail("expected_parameter_count", metadata.parameters().len()), + ); } let predictions: SubjectPredictions = equation - .estimate_predictions(subject, parameters)? + .estimate_predictions(subject, parameters) + .map_err(|error| { + BridgeError::from_anyhow( + BridgeStage::Runtime, + "prediction_estimation_failed", + error, + ) + })? .get_predictions() .into(); Ok(SimulationRow::from_subject_predictions( @@ -54,10 +84,271 @@ pub(crate) fn fit( data: Data, params: List, output_path: &str, -) -> std::result::Result<(), anyhow::Error> +) -> BridgeResult where E: Equation + Clone + Send + 'static + EquationMetadataSource, { - let _result = settings(params, equation, data, output_path)?.fit()?; - Ok(()) + let live_session_id = live_session_id(¶ms).map_err(|error| { + BridgeError::from_anyhow(BridgeStage::Settings, "live_session_settings_invalid", error) + })?; + let (idelta, tad) = runtime_interval(¶ms).map_err(|error| { + BridgeError::from_anyhow(BridgeStage::Settings, "runtime_interval_invalid", error) + })?; + let parameter_names = model_parameters(&equation)?; + let transport_config = fit_payload_config(¶ms, ¶meter_names).map_err(|error| { + BridgeError::from_anyhow(BridgeStage::Settings, "fit_settings_invalid", error) + })?; + let builder = settings(params, equation, data, output_path) + .map_err(|error| BridgeError::from_anyhow(BridgeStage::Settings, "fit_settings_invalid", error))?; + + if let Some(session_id) = live_session_id { + let session = live::session(&session_id).map_err(|error| { + BridgeError::from_anyhow(BridgeStage::Handoff, "live_session_not_available", error) + })?; + let live_result = builder.fit_with_progress_and_control_in_memory( + |event| { + if let Err(error) = session.publish_progress(event) { + tracing::warn!("live session progress publish failed: {}", error); + } + }, + || session.next_control(), + ); + + match live_result { + Ok(mut result) => { + let payload = fit_payload::from_fit_result( + &mut result, + transport_config, + output_path, + idelta, + tad, + ) + .map_err(|error| { + BridgeError::from_anyhow( + BridgeStage::Handoff, + "fit_payload_build_failed", + error, + ) + })?; + return Ok(payload); + } + Err(error) => { + let _ = session.publish_fit_failed(&error.to_string()); + return Err(BridgeError::from_anyhow( + BridgeStage::Runtime, + "fit_execution_failed", + error, + )); + } + } + } + + let mut result = builder.fit_in_memory().map_err(|error| { + BridgeError::from_anyhow(BridgeStage::Runtime, "fit_execution_failed", error) + })?; + let payload = fit_payload::from_fit_result(&mut result, transport_config, output_path, idelta, tad) + .map_err(|error| { + BridgeError::from_anyhow(BridgeStage::Handoff, "fit_payload_build_failed", error) + })?; + // write_outputs() removed: output writing is now explicit, not automatic + Ok(payload) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::fit_payload; + use crate::live; + use extendr_api::prelude::*; + use std::fs; + use std::io::{BufRead, BufReader}; + use std::net::TcpStream; + use std::sync::Once; + use std::time::{Duration, SystemTime, UNIX_EPOCH}; + + fn ensure_r() { + static START_R: Once = Once::new(); + START_R.call_once(|| { + extendr_engine::start_r(); + }); + } + + fn equation() -> equation::ODE { + equation::ODE::new( + |x, p, _t, dx, b, _rateiv, _cov| { + fetch_params!(p, ke); + dx[0] = -ke * x[0] + b[0]; + }, + |_p, _t, _cov| lag! {}, + |_p, _t, _cov| fa! {}, + |_p, _t, _cov, _x| {}, + |x, p, _t, _cov, y| { + fetch_params!(p, v); + y[0] = x[0] / v; + }, + ) + .with_nstates(1) + .with_ndrugs(1) + .with_nout(1) + .with_metadata( + equation::metadata::new("executor_live_fit") + .parameters(["ke", "v"]) + .states(["central"]) + .outputs(["0"]) + .route(equation::Route::bolus("0").to_state("central")), + ) + .expect("executor live-fit test metadata should validate") + } + + fn data() -> Data { + Data::new(vec![Subject::builder("1") + .bolus(0.0, 100.0, 0) + .observation(1.0, 10.0, 0) + .observation(2.0, 7.0, 0) + .build()]) + } + + fn params(live_session_id: Option<&str>) -> List { + ensure_r(); + + let error_model = List::from_pairs(vec![ + ("initial", r!(2.0)), + ("type", r!(["additive"])), + ("fixed", r!(false)), + ("coeff", r!([0.0, 0.10, 0.0, 0.0])), + ]); + + let mut pairs = vec![ + ( + "ranges", + list!(ke = r!([0.05, 1.0]), v = r!([5.0, 50.0])).into(), + ), + ("algorithm", r!("NPAG")), + ("error_models", List::from_values(vec![error_model]).into()), + ("max_cycles", r!(2.0)), + ("prior", r!("sobol")), + ("points", r!(8.0)), + ("seed", r!(7.0)), + ("cache", r!(false)), + ("progress", r!(false)), + ]; + + if let Some(session_id) = live_session_id { + pairs.push(("live_session_id", r!(session_id))); + } + + List::from_pairs(pairs) + } + + fn output_dir(label: &str) -> String { + let nanos = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system time should be after epoch") + .as_nanos(); + let path = std::env::temp_dir().join(format!( + "pm-rs-executor-{label}-{}-{nanos}", + std::process::id() + )); + fs::create_dir_all(&path).expect("test output directory should be created"); + path.to_string_lossy().into_owned() + } + + #[test] + fn fit_streams_cycle_events_to_live_session() -> Result<()> { + let session = live::create_live_session()?; + let stream = TcpStream::connect(("127.0.0.1", session.port()))?; + stream.set_read_timeout(Some(Duration::from_secs(5)))?; + let reader_handle = std::thread::spawn(move || -> Result> { + let mut reader = BufReader::new(stream); + let mut lines = Vec::new(); + + for _ in 0..32 { + let mut line = String::new(); + match reader.read_line(&mut line) { + Ok(0) => break, + Ok(_) => { + let line = line.trim(); + if !line.is_empty() { + lines.push(line.to_string()); + } + } + Err(error) + if matches!( + error.kind(), + std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut + ) => + { + break; + } + Err(error) => return Err(error.into()), + } + } + + Ok(lines) + }); + + let output_path = output_dir("live-fit"); + let payload = fit(equation(), data(), params(Some(session.id())), &output_path)?; + + assert_eq!(payload.transport, fit_payload::FitTransportMode::Inline); + assert!(!payload + .payload + .as_ref() + .expect("inline transport should keep the payload in memory") + .predictions + .is_empty()); + + let backlog = session.backlog_snapshot(); + assert!(backlog.iter().any(|line| line.contains("\"fit_started\""))); + + live::close_live_session(session.id())?; + let lines = reader_handle + .join() + .expect("live session reader thread should not panic")?; + let saw_cycle = lines.iter().any(|line| { + serde_json::from_str::(line) + .map(|message| { + message["kind"] == "progress" + && message["event"]["kind"] == "nonparametric_cycle" + }) + .unwrap_or(false) + }); + + assert!(saw_cycle); + assert!(std::path::Path::new(&output_path) + .join("iterations.csv") + .exists()); + Ok(()) + } + + #[test] + fn fit_payload_builds_without_output_files() -> Result<()> { + let output_path = output_dir("in-memory-payload"); + let builder = settings(params(None), equation(), data(), &output_path)?; + let mut result = builder.fit_in_memory()?; + let config = fit_payload_config(¶ms(None), &model_parameters(&equation())?)?; + + let payload = fit_payload::from_fit_result(&mut result, config, &output_path, 0.12, 0.0)?; + + assert_eq!(payload.transport, fit_payload::FitTransportMode::Inline); + let payload = payload + .payload + .expect("inline transport should keep the payload in memory"); + + assert!(!payload.iterations.is_empty()); + assert!(!payload.theta.is_empty()); + assert!(!payload.posterior.is_empty()); + assert!(!payload.predictions.is_empty()); + assert!(!std::path::Path::new(&output_path) + .join("iterations.csv") + .exists()); + assert!(!std::path::Path::new(&output_path) + .join("settings.json") + .exists()); + assert!(!std::path::Path::new(&output_path) + .join("fit_manifest.json") + .exists()); + + Ok(()) + } } diff --git a/src/rust/src/fit_payload.rs b/src/rust/src/fit_payload.rs new file mode 100644 index 000000000..c65a1e2e2 --- /dev/null +++ b/src/rust/src/fit_payload.rs @@ -0,0 +1,821 @@ +use anyhow::{anyhow, Result}; +use pmcore::estimation::nonparametric::{median, NonparametricWorkspace}; +use pmcore::prelude::{AssayErrorModel, Equation, Event}; +use pmcore::results::FitResult; +use serde::Serialize; +use serde_json::{Map, Value}; +use sha2::{Digest, Sha256}; +use std::collections::HashMap; +use std::fs::{self, File}; +use std::io::{BufWriter, Write}; +use std::path::Path; + +const FIT_RESULT_SCHEMA_VERSION: u32 = 1; +const MANIFEST_FILE_NAME: &str = "fit_manifest.json"; +const POSTERIOR_MANIFEST_ROW_LIMIT: usize = 100_000; +const PREDICTION_MANIFEST_ROW_LIMIT: usize = 500_000; +const INLINE_JSON_LIMIT_BYTES: usize = 50 * 1024 * 1024; + +#[derive(Debug, Clone, Serialize)] +pub(crate) struct FitPayload { + pub(crate) parameter_names: Vec, + pub(crate) covariate_names: Vec, + pub(crate) iterations: Vec>, + pub(crate) theta: Vec>, + pub(crate) posterior: Vec>, + pub(crate) predictions: Vec, + pub(crate) covariates: Vec>, +} + +#[derive(Debug, Clone, Serialize)] +pub(crate) struct FitPayloadConfig { + pub(crate) parameters: Vec>, + pub(crate) errormodels: FitPayloadErrorModels, + pub(crate) prior: Option>>, +} + +#[derive(Debug, Clone, Serialize)] +pub(crate) struct FitPayloadParameter { + pub(crate) name: String, + pub(crate) lower: f64, + pub(crate) upper: f64, +} + +#[derive(Debug, Clone, Serialize)] +pub(crate) struct FitPayloadErrorModels { + pub(crate) models: Vec>, +} + +#[derive(Debug, Clone, Serialize)] +pub(crate) struct FitPayloadErrorModel { + pub(crate) poly: FitPayloadPoly, +} + +#[derive(Debug, Clone, Serialize)] +pub(crate) struct FitPayloadPoly { + pub(crate) c0: f64, + pub(crate) c1: f64, + pub(crate) c2: f64, + pub(crate) c3: f64, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[serde(rename_all = "lowercase")] +pub(crate) enum FitTransportMode { + Inline, + Manifest, +} + +#[derive(Debug, Clone, Serialize)] +pub(crate) struct FitResultEnvelope { + pub(crate) kind: &'static str, + pub(crate) schema_version: u32, + pub(crate) transport: FitTransportMode, + pub(crate) config: FitPayloadConfig, + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) payload: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) manifest: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub(crate) struct FitManifest { + pub(crate) generator: FitManifestGenerator, + pub(crate) tables: FitManifestTables, +} + +#[derive(Debug, Clone, Serialize)] +pub(crate) struct FitManifestGenerator { + pub(crate) name: &'static str, + pub(crate) version: &'static str, +} + +#[derive(Debug, Clone, Serialize)] +pub(crate) struct FitManifestTables { + pub(crate) iterations: FitManifestTable, + pub(crate) theta: FitManifestTable, + pub(crate) posterior: FitManifestTable, + pub(crate) predictions: FitManifestTable, + pub(crate) covariates: FitManifestTable, +} + +#[derive(Debug, Clone, Serialize)] +pub(crate) struct FitManifestTable { + pub(crate) path: String, + pub(crate) row_count: usize, + pub(crate) sha256: String, +} + +#[derive(Debug, Clone, Serialize)] +pub(crate) struct PredictionRow { + id: String, + time: f64, + outeq: usize, + block: usize, + obs: Option, + cens: String, + pop_mean: f64, + pop_median: f64, + post_mean: f64, + post_median: f64, +} + +impl FitResultEnvelope { + fn inline(config: FitPayloadConfig, payload: FitPayload) -> Self { + Self { + kind: "ok", + schema_version: FIT_RESULT_SCHEMA_VERSION, + transport: FitTransportMode::Inline, + config, + payload: Some(payload), + manifest: None, + } + } + + fn manifest(config: FitPayloadConfig, manifest: FitManifest) -> Self { + Self { + kind: "ok", + schema_version: FIT_RESULT_SCHEMA_VERSION, + transport: FitTransportMode::Manifest, + config, + payload: None, + manifest: Some(manifest), + } + } +} + +pub(crate) fn from_fit_result( + result: &mut FitResult, + config: FitPayloadConfig, + output_path: &str, + idelta: f64, + tad: f64, +) -> Result +where + E: Equation, +{ + match result { + FitResult::Nonparametric(workspace) => { + from_nonparametric_workspace(workspace, config, output_path, idelta, tad) + } + } +} + +fn from_nonparametric_workspace( + workspace: &mut NonparametricWorkspace, + config: FitPayloadConfig, + output_path: &str, + idelta: f64, + tad: f64, +) -> Result +where + E: Equation, +{ + let parameter_names = workspace.get_theta().param_names(); + let covariate_names = collect_covariate_names(workspace); + + if workspace.predictions().is_none() { + workspace.calculate_predictions(idelta, tad)?; + } + + let predictions = workspace + .predictions() + .ok_or_else(|| anyhow!("fit predictions were not materialized"))?; + + build_transport( + FitPayload { + parameter_names: parameter_names.clone(), + covariate_names: covariate_names.clone(), + iterations: iteration_rows(workspace, ¶meter_names)?, + theta: theta_rows(workspace, ¶meter_names), + posterior: posterior_rows(workspace, ¶meter_names), + predictions: prediction_rows(predictions), + covariates: covariate_rows(workspace, &covariate_names), + }, + config, + output_path, + ) +} + +fn build_transport( + payload: FitPayload, + config: FitPayloadConfig, + output_path: &str, +) -> Result { + match select_transport(&payload, &config)? { + FitTransportMode::Inline => { + let _ = fs::remove_file(Path::new(output_path).join(MANIFEST_FILE_NAME)); + Ok(FitResultEnvelope::inline(config, payload)) + } + FitTransportMode::Manifest => build_manifest_transport(payload, config, output_path), + } +} + +fn select_transport(payload: &FitPayload, config: &FitPayloadConfig) -> Result { + if payload.posterior.len() >= POSTERIOR_MANIFEST_ROW_LIMIT + || payload.predictions.len() >= PREDICTION_MANIFEST_ROW_LIMIT + { + return Ok(FitTransportMode::Manifest); + } + + if count_inline_envelope_bytes(payload, config)? >= INLINE_JSON_LIMIT_BYTES { + return Ok(FitTransportMode::Manifest); + } + + Ok(FitTransportMode::Inline) +} + +fn count_inline_envelope_bytes(payload: &FitPayload, config: &FitPayloadConfig) -> Result { + #[derive(Serialize)] + struct InlineEnvelopeRef<'a> { + kind: &'static str, + schema_version: u32, + transport: &'static str, + config: &'a FitPayloadConfig, + payload: &'a FitPayload, + } + + let mut writer = CountingWriter::default(); + serde_json::to_writer( + &mut writer, + &InlineEnvelopeRef { + kind: "ok", + schema_version: FIT_RESULT_SCHEMA_VERSION, + transport: "inline", + config, + payload, + }, + ) + .map_err(|error| anyhow!("Failed to estimate inline fit payload size: {}", error))?; + Ok(writer.bytes) +} + +fn build_manifest_transport( + payload: FitPayload, + config: FitPayloadConfig, + output_path: &str, +) -> Result { + fs::create_dir_all(output_path)?; + + let tables = write_manifest_tables(&payload, &config, output_path)?; + let envelope = FitResultEnvelope::manifest( + config, + FitManifest { + generator: FitManifestGenerator { + name: env!("CARGO_PKG_NAME"), + version: env!("CARGO_PKG_VERSION"), + }, + tables, + }, + ); + + let manifest_path = Path::new(output_path).join(MANIFEST_FILE_NAME); + fs::write( + manifest_path, + serde_json::to_vec_pretty(&envelope) + .map_err(|error| anyhow!("Failed to serialize fit manifest: {}", error))?, + )?; + + Ok(envelope) +} + +fn write_manifest_tables( + payload: &FitPayload, + config: &FitPayloadConfig, + output_path: &str, +) -> Result { + Ok(FitManifestTables { + iterations: write_map_table( + output_path, + "iterations.csv", + &iteration_columns(payload, config), + &payload.iterations, + )?, + theta: write_map_table(output_path, "theta.csv", &theta_columns(payload), &payload.theta)?, + posterior: write_map_table( + output_path, + "posterior.csv", + &posterior_columns(payload), + &payload.posterior, + )?, + predictions: write_predictions_table(output_path, "predictions.csv", &payload.predictions)?, + covariates: write_map_table( + output_path, + "covariates.csv", + &covariate_columns(payload), + &payload.covariates, + )?, + }) +} + +fn iteration_columns(payload: &FitPayload, config: &FitPayloadConfig) -> Vec { + let mut columns = vec![ + "cycle".to_string(), + "converged".to_string(), + "status".to_string(), + "neg2ll".to_string(), + "nspp".to_string(), + ]; + + for index in 0..config.errormodels.models.len() { + columns.push(format!("gamlam.{index}")); + } + + for parameter_name in &payload.parameter_names { + columns.push(format!("{parameter_name}.mean")); + columns.push(format!("{parameter_name}.median")); + columns.push(format!("{parameter_name}.sd")); + } + + columns +} + +fn theta_columns(payload: &FitPayload) -> Vec { + let mut columns = payload.parameter_names.clone(); + columns.push("prob".to_string()); + columns +} + +fn posterior_columns(payload: &FitPayload) -> Vec { + let mut columns = vec!["id".to_string(), "point".to_string()]; + columns.extend(payload.parameter_names.clone()); + columns.push("prob".to_string()); + columns +} + +fn covariate_columns(payload: &FitPayload) -> Vec { + let mut columns = vec!["id".to_string(), "time".to_string(), "block".to_string()]; + columns.extend(payload.covariate_names.clone()); + columns +} + +fn write_map_table( + output_path: &str, + file_name: &str, + columns: &[String], + rows: &[Map], +) -> Result { + let path = Path::new(output_path).join(file_name); + let hashing_writer = HashingWriter::new(BufWriter::new(File::create(&path)?)); + let mut writer = csv::Writer::from_writer(hashing_writer); + + writer.write_record(columns)?; + for row in rows { + let record = columns + .iter() + .map(|column| json_value_to_csv_cell(row.get(column).unwrap_or(&Value::Null))) + .collect::>(); + writer.write_record(record)?; + } + + let hashing_writer = writer + .into_inner() + .map_err(|error| anyhow!("Failed to finalize manifest CSV {}: {}", file_name, error.error()))?; + + Ok(FitManifestTable { + path: file_name.to_string(), + row_count: rows.len(), + sha256: hashing_writer.finish_hex()?, + }) +} + +fn write_predictions_table( + output_path: &str, + file_name: &str, + rows: &[PredictionRow], +) -> Result { + let columns = [ + "id", + "time", + "outeq", + "block", + "obs", + "cens", + "pop_mean", + "pop_median", + "post_mean", + "post_median", + ]; + let path = Path::new(output_path).join(file_name); + let hashing_writer = HashingWriter::new(BufWriter::new(File::create(&path)?)); + let mut writer = csv::Writer::from_writer(hashing_writer); + + writer.write_record(columns)?; + for row in rows { + writer.write_record([ + row.id.clone(), + row.time.to_string(), + row.outeq.to_string(), + row.block.to_string(), + row.obs.map(|value| value.to_string()).unwrap_or_default(), + row.cens.clone(), + row.pop_mean.to_string(), + row.pop_median.to_string(), + row.post_mean.to_string(), + row.post_median.to_string(), + ])?; + } + + let hashing_writer = writer + .into_inner() + .map_err(|error| anyhow!("Failed to finalize manifest CSV {}: {}", file_name, error.error()))?; + + Ok(FitManifestTable { + path: file_name.to_string(), + row_count: rows.len(), + sha256: hashing_writer.finish_hex()?, + }) +} + +fn json_value_to_csv_cell(value: &Value) -> String { + match value { + Value::Null => String::new(), + Value::Bool(value) => value.to_string(), + Value::Number(value) => value.to_string(), + Value::String(value) => value.clone(), + other => serde_json::to_string(other).unwrap_or_default(), + } +} + +#[derive(Default)] +struct CountingWriter { + bytes: usize, +} + +impl Write for CountingWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.bytes += buf.len(); + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +struct HashingWriter { + inner: W, + hasher: Sha256, +} + +impl HashingWriter { + fn new(inner: W) -> Self { + Self { + inner, + hasher: Sha256::new(), + } + } +} + +impl HashingWriter { + fn finish_hex(mut self) -> Result { + self.inner.flush()?; + Ok(format!("{:x}", self.hasher.finalize())) + } +} + +impl Write for HashingWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.hasher.update(buf); + self.inner.write(buf) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.inner.flush() + } +} + +fn collect_covariate_names(workspace: &NonparametricWorkspace) -> Vec +where + E: Equation, +{ + let mut names = workspace + .data() + .subjects() + .iter() + .flat_map(|subject| subject.occasions().iter()) + .flat_map(|occasion| { + occasion + .covariates() + .covariates() + .keys() + .cloned() + .collect::>() + }) + .collect::>(); + names.sort(); + names.dedup(); + names +} + +fn iteration_rows( + workspace: &NonparametricWorkspace, + parameter_names: &[String], +) -> Result>> +where + E: Equation, +{ + workspace + .cycle_log() + .cycles() + .iter() + .map(|cycle| { + let mut row = Map::new(); + row.insert("cycle".to_string(), Value::from(cycle.cycle() as u64)); + row.insert( + "converged".to_string(), + Value::from(cycle.status().to_string() == "Converged"), + ); + row.insert("status".to_string(), Value::from(cycle.status().to_string())); + row.insert("neg2ll".to_string(), Value::from(cycle.objf())); + row.insert("nspp".to_string(), Value::from(cycle.theta().nspp() as u64)); + + for (outeq, error_model) in cycle.error_models().iter() { + if matches!(error_model, AssayErrorModel::None) { + continue; + } + + let factor = (error_model.factor()? * 100000.0).round() / 100000.0; + row.insert(format!("gamlam.{outeq}"), Value::from(factor)); + } + + for (column_index, parameter_name) in parameter_names.iter().enumerate() { + let values = cycle + .theta() + .matrix() + .col(column_index) + .iter() + .copied() + .collect::>(); + let mean = values.iter().sum::() / values.len() as f64; + let variance = values.iter().map(|value| (value - mean).powi(2)).sum::() + / (values.len() as f64 - 1.0); + + row.insert(format!("{parameter_name}.mean"), Value::from(mean)); + row.insert( + format!("{parameter_name}.median"), + Value::from(median(&values)), + ); + row.insert(format!("{parameter_name}.sd"), Value::from(variance)); + } + + Ok(row) + }) + .collect() +} + +fn theta_rows(workspace: &NonparametricWorkspace, parameter_names: &[String]) -> Vec> +where + E: Equation, +{ + workspace + .get_theta() + .matrix() + .row_iter() + .zip(workspace.weights().iter()) + .map(|(theta_row, weight)| { + let mut row = Map::new(); + for (parameter_name, value) in parameter_names.iter().zip(theta_row.iter()) { + row.insert(parameter_name.clone(), Value::from(*value)); + } + row.insert("prob".to_string(), Value::from(weight)); + row + }) + .collect() +} + +fn posterior_rows( + workspace: &NonparametricWorkspace, + parameter_names: &[String], +) -> Vec> +where + E: Equation, +{ + let subjects = workspace.data().subjects(); + + workspace + .posterior() + .matrix() + .row_iter() + .enumerate() + .flat_map(|(subject_index, posterior_row)| { + let id = subjects[subject_index].id().clone(); + posterior_row.iter().enumerate().map(move |(point_index, probability)| { + let mut row = Map::new(); + row.insert("id".to_string(), Value::from(id.clone())); + row.insert("point".to_string(), Value::from(point_index as u64)); + + for (parameter_name, value) in parameter_names.iter().zip( + workspace.get_theta().matrix().row(point_index).iter(), + ) { + row.insert(parameter_name.clone(), Value::from(*value)); + } + + row.insert("prob".to_string(), Value::from(*probability)); + row + }) + }) + .collect() +} + +fn prediction_rows( + predictions: &pmcore::estimation::nonparametric::NPPredictions, +) -> Vec { + predictions + .predictions() + .iter() + .map(|row| PredictionRow { + id: row.id().to_string(), + time: row.time(), + outeq: row.outeq(), + block: row.block(), + obs: row.obs(), + cens: match row.censoring() { + pmcore::prelude::Censor::None => "none".to_string(), + pmcore::prelude::Censor::BLOQ => "bloq".to_string(), + pmcore::prelude::Censor::ALOQ => "aloq".to_string(), + }, + pop_mean: row.pop_mean(), + pop_median: row.pop_median(), + post_mean: row.post_mean(), + post_median: row.post_median(), + }) + .collect() +} + +fn covariate_rows( + workspace: &NonparametricWorkspace, + covariate_names: &[String], +) -> Vec> +where + E: Equation, +{ + workspace + .data() + .subjects() + .iter() + .flat_map(|subject| { + subject.occasions().iter().flat_map(move |occasion| { + let covariates = occasion.covariates().covariates(); + + occasion.iter().map(move |event| { + let time = match event { + Event::Bolus(bolus) => bolus.time(), + Event::Infusion(infusion) => infusion.time(), + Event::Observation(observation) => observation.time(), + }; + + let mut row = Map::new(); + row.insert("id".to_string(), Value::from(subject.id().clone())); + row.insert("time".to_string(), Value::from(time)); + row.insert("block".to_string(), Value::from(occasion.index() as u64)); + + for covariate_name in covariate_names { + let value = covariates + .get(covariate_name) + .and_then(|covariate| covariate.interpolate(time).ok()); + + row.insert( + covariate_name.clone(), + value.map(Value::from).unwrap_or(Value::Null), + ); + } + + row + }) + }) + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + use std::time::{SystemTime, UNIX_EPOCH}; + + fn sample_config() -> FitPayloadConfig { + let mut error_model = HashMap::new(); + error_model.insert( + "Additive".to_string(), + FitPayloadErrorModel { + poly: FitPayloadPoly { + c0: 0.0, + c1: 0.1, + c2: 0.0, + c3: 0.0, + }, + }, + ); + + let mut prior = HashMap::new(); + prior.insert("Sobol".to_string(), vec![20.0]); + + FitPayloadConfig { + parameters: vec![vec![ + FitPayloadParameter { + name: "ke".to_string(), + lower: 0.1, + upper: 1.0, + }, + FitPayloadParameter { + name: "v".to_string(), + lower: 1.0, + upper: 10.0, + }, + ]], + errormodels: FitPayloadErrorModels { + models: vec![error_model], + }, + prior: Some(prior), + } + } + + fn sample_payload(posterior_rows: usize) -> FitPayload { + FitPayload { + parameter_names: vec!["ke".to_string(), "v".to_string()], + covariate_names: vec!["cov1".to_string()], + iterations: vec![Map::from_iter([ + ("cycle".to_string(), Value::from(1_u64)), + ("converged".to_string(), Value::from(false)), + ("status".to_string(), Value::from("payload")), + ("neg2ll".to_string(), Value::from(123.0)), + ("nspp".to_string(), Value::from(2_u64)), + ("gamlam.0".to_string(), Value::from(0.5)), + ("ke.mean".to_string(), Value::from(2.0)), + ("ke.median".to_string(), Value::from(2.0)), + ("ke.sd".to_string(), Value::from(0.25)), + ("v.mean".to_string(), Value::from(4.0)), + ("v.median".to_string(), Value::from(4.0)), + ("v.sd".to_string(), Value::from(0.5)), + ])], + theta: vec![ + Map::from_iter([ + ("ke".to_string(), Value::from(2.0)), + ("v".to_string(), Value::from(4.0)), + ("prob".to_string(), Value::from(0.6)), + ]), + Map::from_iter([ + ("ke".to_string(), Value::from(3.0)), + ("v".to_string(), Value::from(5.0)), + ("prob".to_string(), Value::from(0.4)), + ]), + ], + posterior: (0..posterior_rows) + .map(|index| { + Map::from_iter([ + ("id".to_string(), Value::from("1")), + ("point".to_string(), Value::from(index as u64)), + ("ke".to_string(), Value::from(2.0)), + ("v".to_string(), Value::from(4.0)), + ("prob".to_string(), Value::from(0.7)), + ]) + }) + .collect(), + predictions: vec![PredictionRow { + id: "1".to_string(), + time: 0.0, + outeq: 0, + block: 0, + obs: Some(10.0), + cens: "none".to_string(), + pop_mean: 11.0, + pop_median: 12.0, + post_mean: 13.0, + post_median: 14.0, + }], + covariates: vec![Map::from_iter([ + ("id".to_string(), Value::from("1")), + ("time".to_string(), Value::from(0.0)), + ("block".to_string(), Value::from(0_u64)), + ("cov1".to_string(), Value::from(5.0)), + ])], + } + } + + fn output_dir(label: &str) -> String { + let nanos = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system time should be after epoch") + .as_nanos(); + let path = std::env::temp_dir().join(format!( + "pm-rs-fit-payload-{label}-{}-{nanos}", + std::process::id() + )); + fs::create_dir_all(&path).expect("test output directory should be created"); + path.to_string_lossy().into_owned() + } + + #[test] + fn payload_switches_to_manifest_at_posterior_threshold() -> Result<()> { + let output_path = output_dir("manifest-threshold"); + let envelope = build_transport(sample_payload(POSTERIOR_MANIFEST_ROW_LIMIT), sample_config(), &output_path)?; + + assert_eq!(envelope.transport, FitTransportMode::Manifest); + assert!(envelope.payload.is_none()); + let manifest = envelope.manifest.expect("manifest transport should include a manifest"); + assert_eq!(manifest.tables.posterior.row_count, POSTERIOR_MANIFEST_ROW_LIMIT); + assert!(Path::new(&output_path).join(MANIFEST_FILE_NAME).exists()); + assert!(Path::new(&output_path).join("posterior.csv").exists()); + Ok(()) + } +} \ No newline at end of file diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index 88f3cc603..bbe72f8dc 100755 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -1,9 +1,13 @@ +mod bridge_errors; mod executor; +mod fit_payload; +mod live; mod logs; mod settings; mod simulation; use anyhow::{anyhow, Result as AnyResult}; +use bridge_errors::{BridgeError, BridgeResult, BridgeStage}; use extendr_api::prelude::*; use pmcore::api::LoggingLevel; use pmcore::prelude::{ @@ -18,32 +22,42 @@ use tracing_subscriber::layer::SubscriberExt; use crate::logs::{configure_logs, LogControls, RFormatLayer}; -fn to_r_result(result: AnyResult) -> extendr_api::Result { - result.map_err(|error| extendr_api::Error::Other(error.to_string())) +fn to_r_result(result: BridgeResult) -> extendr_api::Result { + result.map_err(Into::into) } -fn validate_data_path(data_path: &str) -> AnyResult<()> { +fn validate_data_path(data_path: &str) -> BridgeResult<()> { if !Path::new(data_path).exists() { - return Err(anyhow!("Data path does not exist: {}", data_path)); + return Err( + BridgeError::new( + BridgeStage::Data, + "data_path_missing", + format!("Data path does not exist: {}", data_path), + ) + .with_detail("data_path", data_path), + ); } Ok(()) } -fn read_pmetrics_data(data_path: &str) -> AnyResult { - read_pmetrics(data_path).map_err(|err| anyhow!("Failed to parse data: {}", err)) +fn read_pmetrics_data(data_path: &str) -> BridgeResult { + read_pmetrics(data_path).map_err(|error| { + BridgeError::new( + BridgeStage::Data, + "data_parse_failed", + format!("Failed to parse data: {}", error), + ) + .with_detail("data_path", data_path) + }) } -fn format_runtime_compile_error(error: &dsl::RuntimeError, model_source: &str) -> String { - match error +fn compile_diagnostic(error: &dsl::RuntimeError, model_source: &str) -> Option { + error .render_diagnostic(model_source) .filter(|message| !message.trim().is_empty()) - { - Some(diagnostic) => format!("{}\n{}", error, diagnostic), - None => error.to_string(), - } } -fn parse_ode_solver(solver: Option) -> AnyResult> { +fn parse_ode_solver(solver: Option) -> BridgeResult> { match solver { None => Ok(None), Some(solver) if solver.trim().is_empty() => Ok(None), @@ -52,7 +66,14 @@ fn parse_ode_solver(solver: Option) -> AnyResult> { "TRBDF2" => Ok(Some(OdeSolver::Sdirk(SdirkTableau::TrBdf2))), "ESDIRK34" => Ok(Some(OdeSolver::Sdirk(SdirkTableau::Esdirk34))), "TSIT45" => Ok(Some(OdeSolver::ExplicitRk(ExplicitRkTableau::Tsit45))), - other => Err(anyhow!("Unsupported ODE solver: {}", other)), + other => Err( + BridgeError::new( + BridgeStage::Settings, + "unsupported_ode_solver", + format!("Unsupported ODE solver: {}", other), + ) + .with_detail("solver", other), + ), }, } } @@ -81,15 +102,28 @@ fn compile_runtime_model( model_source: &str, kind: &str, solver: Option, -) -> AnyResult { +) -> BridgeResult { let model_source = model_source.trim(); if model_source.is_empty() { - return Err(anyhow!("Model source is empty")); + return Err(BridgeError::new( + BridgeStage::Compile, + "model_source_empty", + "Model source is empty", + )); } match kind { "ode" | "analytical" => {} - other => return Err(anyhow!("{} is not a supported model type", other)), + other => { + return Err( + BridgeError::new( + BridgeStage::Compile, + "unsupported_model_type", + format!("{} is not a supported model type", other), + ) + .with_detail("model_kind", other), + ) + } } let compiled = dsl::compile_module_source_to_runtime( @@ -98,33 +132,60 @@ fn compile_runtime_model( RuntimeCompilationTarget::Jit, |_stage, _message| {}, ) - .map_err(|error| anyhow!(format_runtime_compile_error(&error, model_source)))?; + .map_err(|error| { + let message = error.to_string(); + let diagnostic = compile_diagnostic(&error, model_source); + let mut bridge_error = BridgeError::new(BridgeStage::Compile, "runtime_compile_failed", message) + .with_detail("model_kind", kind); + if let Some(diagnostic) = diagnostic { + bridge_error = bridge_error.with_diagnostic(diagnostic); + } + bridge_error + })?; let actual_kind = compiled_model_kind(&compiled); if actual_kind != kind { - return Err(anyhow!( - "Model source kind mismatch: expected {}, got {}", - kind, - actual_kind - )); + return Err( + BridgeError::new( + BridgeStage::Compile, + "model_kind_mismatch", + format!( + "Model source kind mismatch: expected {}, got {}", + kind, actual_kind + ), + ) + .with_detail("expected_kind", kind) + .with_detail("actual_kind", actual_kind), + ); } let solver = parse_ode_solver(solver)?; if solver.is_some() && actual_kind != "ode" { - return Err(anyhow!( - "ODE solver selection is only supported for ODE models" - )); + return Err( + BridgeError::new( + BridgeStage::Settings, + "ode_solver_requires_ode_model", + "ODE solver selection is only supported for ODE models", + ) + .with_detail("actual_kind", actual_kind), + ); } Ok(apply_ode_solver(compiled, solver)) } -fn parse_theta(matrix: RMatrix) -> AnyResult>> { +fn parse_theta(matrix: RMatrix) -> BridgeResult>> { let nspp = matrix.nrows(); let ndim = matrix.ncols(); let real_vector = matrix .as_real_vector() - .ok_or_else(|| anyhow!("theta matrix must contain real values"))?; + .ok_or_else(|| { + BridgeError::new( + BridgeStage::Data, + "theta_matrix_not_real", + "theta matrix must contain real values", + ) + })?; let mut theta = vec![vec![0.0; ndim]; nspp]; for i in 0..nspp { for j in 0..ndim { @@ -158,20 +219,32 @@ fn simulate_one( solver: Nullable, ) -> Dataframe { extendr_api::error::unwrap_or_throw_error(to_r_result( - (|| -> AnyResult> { + (|| -> BridgeResult> { validate_data_path(data_path)?; let data = read_pmetrics_data(data_path)?; let compiled_model = compile_runtime_model(model_source, kind, solver.into_option())?; let subjects = data.subjects(); let first_subject = subjects .first() - .ok_or_else(|| anyhow!("Data set contains no subjects"))?; + .ok_or_else(|| { + BridgeError::new( + BridgeStage::Data, + "data_subjects_missing", + "Data set contains no subjects", + ) + })?; let rows = match compiled_model { CompiledRuntimeModel::Ode(equation) => { let metadata = equation .equation_metadata() - .ok_or_else(|| anyhow!("runtime model metadata is required"))?; + .ok_or_else(|| { + BridgeError::new( + BridgeStage::Runtime, + "runtime_metadata_missing", + "runtime model metadata is required", + ) + })?; let parameters = Parameters::with_model( &equation, metadata @@ -179,13 +252,26 @@ fn simulate_one( .iter() .zip(spp.iter()) .map(|(parameter, value)| (parameter.name(), *value)), - )?; + ) + .map_err(|error| { + BridgeError::from_anyhow( + BridgeStage::Runtime, + "parameter_binding_failed", + error, + ) + })?; executor::simulate(&equation, first_subject, ¶meters, 0)? } CompiledRuntimeModel::Analytical(equation) => { let metadata = equation .equation_metadata() - .ok_or_else(|| anyhow!("runtime model metadata is required"))?; + .ok_or_else(|| { + BridgeError::new( + BridgeStage::Runtime, + "runtime_metadata_missing", + "runtime model metadata is required", + ) + })?; let parameters = Parameters::with_model( &equation, metadata @@ -193,18 +279,33 @@ fn simulate_one( .iter() .zip(spp.iter()) .map(|(parameter, value)| (parameter.name(), *value)), - )?; + ) + .map_err(|error| { + BridgeError::from_anyhow( + BridgeStage::Runtime, + "parameter_binding_failed", + error, + ) + })?; executor::simulate(&equation, first_subject, ¶meters, 0)? } CompiledRuntimeModel::Sde(_) => { - return Err(anyhow!( - "SDE models are not supported for simulation in this Pmetrics runtime" + return Err(BridgeError::new( + BridgeStage::Runtime, + "simulation_model_kind_unsupported", + "SDE models are not supported for simulation in this Pmetrics runtime", )); } }; rows.into_dataframe() - .map_err(|error| anyhow!("Failed to build data frame: {}", error)) + .map_err(|error| { + BridgeError::from_anyhow( + BridgeStage::Handoff, + "simulation_dataframe_build_failed", + anyhow!("Failed to build data frame: {}", error), + ) + }) })(), )) } @@ -228,7 +329,7 @@ fn simulate_all( use rayon::prelude::*; extendr_api::error::unwrap_or_throw_error(to_r_result( - (|| -> AnyResult> { + (|| -> BridgeResult> { validate_data_path(data_path)?; let theta = parse_theta(theta)?; let data = read_pmetrics_data(data_path)?; @@ -243,7 +344,13 @@ fn simulate_all( let equation = equation.clone(); let metadata = equation .equation_metadata() - .ok_or_else(|| anyhow!("runtime model metadata is required"))?; + .ok_or_else(|| { + BridgeError::new( + BridgeStage::Runtime, + "runtime_metadata_missing", + "runtime model metadata is required", + ) + })?; let parameters = Parameters::with_model( &equation, metadata @@ -251,14 +358,21 @@ fn simulate_all( .iter() .zip(spp.iter()) .map(|(parameter, value)| (parameter.name(), *value)), - )?; + ) + .map_err(|error| { + BridgeError::from_anyhow( + BridgeStage::Runtime, + "parameter_binding_failed", + error, + ) + })?; subjects .iter() .map(|subject| executor::simulate(&equation, subject, ¶meters, i)) - .collect::>>() + .collect::>>() .map(|rows| rows.into_iter().flatten().collect::>()) }) - .collect::>>()? + .collect::>>()? .into_iter() .flatten() .collect(), @@ -269,7 +383,13 @@ fn simulate_all( let equation = equation.clone(); let metadata = equation .equation_metadata() - .ok_or_else(|| anyhow!("runtime model metadata is required"))?; + .ok_or_else(|| { + BridgeError::new( + BridgeStage::Runtime, + "runtime_metadata_missing", + "runtime model metadata is required", + ) + })?; let parameters = Parameters::with_model( &equation, metadata @@ -277,26 +397,41 @@ fn simulate_all( .iter() .zip(spp.iter()) .map(|(parameter, value)| (parameter.name(), *value)), - )?; + ) + .map_err(|error| { + BridgeError::from_anyhow( + BridgeStage::Runtime, + "parameter_binding_failed", + error, + ) + })?; subjects .iter() .map(|subject| executor::simulate(&equation, subject, ¶meters, i)) - .collect::>>() + .collect::>>() .map(|rows| rows.into_iter().flatten().collect::>()) }) - .collect::>>()? + .collect::>>()? .into_iter() .flatten() .collect(), CompiledRuntimeModel::Sde(_) => { - return Err(anyhow!( - "SDE models are not supported for simulation in this Pmetrics runtime" + return Err(BridgeError::new( + BridgeStage::Runtime, + "simulation_model_kind_unsupported", + "SDE models are not supported for simulation in this Pmetrics runtime", )); } }; rows.into_dataframe() - .map_err(|error| anyhow!("Failed to build data frame: {}", error)) + .map_err(|error| { + BridgeError::from_anyhow( + BridgeStage::Handoff, + "simulation_dataframe_build_failed", + anyhow!("Failed to build data frame: {}", error), + ) + }) })(), )) } @@ -318,18 +453,22 @@ pub fn fit( output_path: &str, kind: &str, solver: Nullable, -) { - extendr_api::error::unwrap_or_throw_error(to_r_result((|| -> AnyResult<()> { - let log_controls = parse_log_controls(¶ms, output_path)?; +) -> String { + extendr_api::error::unwrap_or_throw_error(to_r_result((|| -> BridgeResult { + let log_controls = parse_log_controls(¶ms, output_path).map_err(|error| { + BridgeError::from_anyhow(BridgeStage::Settings, "log_controls_invalid", error) + })?; RFormatLayer::reset_global_timer(); - setup_logs_inner(&log_controls)?; + setup_logs_inner(&log_controls).map_err(|error| { + BridgeError::from_anyhow(BridgeStage::Settings, "log_setup_failed", error) + })?; println!("Initializing model fit..."); validate_data_path(data)?; let data = read_pmetrics_data(data)?; let compiled_model = compile_runtime_model(model_source, kind, solver.into_option())?; - match compiled_model { + let payload = match compiled_model { CompiledRuntimeModel::Ode(equation) => { executor::fit(equation, data, params, output_path)? } @@ -337,13 +476,21 @@ pub fn fit( executor::fit(equation, data, params, output_path)? } CompiledRuntimeModel::Sde(_) => { - return Err(anyhow!( - "SDE models are not supported for fitting in this Pmetrics runtime" + return Err(BridgeError::new( + BridgeStage::Runtime, + "fit_model_kind_unsupported", + "SDE models are not supported for fitting in this Pmetrics runtime", )); } - } - - Ok(()) + }; + + serde_json::to_string(&payload).map_err(|error| { + BridgeError::from_anyhow( + BridgeStage::Handoff, + "fit_payload_serialize_failed", + anyhow!("Failed to serialize fit payload: {}", error), + ) + }) })())) } @@ -362,12 +509,14 @@ fn is_cargo_installed() -> bool { /// @export #[extendr] fn model_parameters(model_source: &str, kind: &str) -> Vec { - extendr_api::error::unwrap_or_throw_error(to_r_result((|| -> AnyResult> { + extendr_api::error::unwrap_or_throw_error(to_r_result((|| -> BridgeResult> { match compile_runtime_model(model_source, kind, None)? { CompiledRuntimeModel::Ode(equation) => executor::model_parameters(&equation), CompiledRuntimeModel::Analytical(equation) => executor::model_parameters(&equation), - CompiledRuntimeModel::Sde(_) => Err(anyhow!( - "SDE models are not supported for parameter inspection in this Pmetrics runtime" + CompiledRuntimeModel::Sde(_) => Err(BridgeError::new( + BridgeStage::Runtime, + "parameter_inspection_model_kind_unsupported", + "SDE models are not supported for parameter inspection in this Pmetrics runtime", )), } })())) @@ -445,7 +594,106 @@ fn parse_log_controls(params: &List, output_path: &str) -> AnyResult List { + extendr_api::error::unwrap_or_throw_error(to_r_result( + live::start_live_session().map_err(|error| { + BridgeError::from_anyhow(BridgeStage::Handoff, "live_session_start_failed", error) + }), + )) +} + +#[extendr] +fn wait_live_session_connected(session_id: &str, timeout_ms: i32) -> bool { + extendr_api::error::unwrap_or_throw_error(to_r_result(live::wait_live_session_connected( + session_id, timeout_ms, + ) + .map_err(|error| { + BridgeError::from_anyhow( + BridgeStage::Handoff, + "live_session_wait_failed", + error, + ) + }))) +} + +#[extendr] +fn close_live_session(session_id: &str) { + extendr_api::error::unwrap_or_throw_error(to_r_result( + live::close_live_session(session_id).map_err(|error| { + BridgeError::from_anyhow(BridgeStage::Handoff, "live_session_close_failed", error) + }), + )) +} + +#[extendr] +fn publish_live_report_result(session_id: &str, result_payload: &str, report_generated_at: &str) { + extendr_api::error::unwrap_or_throw_error(to_r_result(live::publish_live_report_result( + session_id, + result_payload, + report_generated_at, + ) + .map_err(|error| { + BridgeError::from_anyhow( + BridgeStage::Handoff, + "live_report_publish_failed", + error, + ) + }))) +} + +#[extendr] +fn publish_live_report_failed(session_id: &str, message: &str) { + extendr_api::error::unwrap_or_throw_error(to_r_result(live::publish_live_report_failed( + session_id, message, + ) + .map_err(|error| { + BridgeError::from_anyhow( + BridgeStage::Handoff, + "live_report_failure_publish_failed", + error, + ) + }))) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn bridge_error_compile_stage_tags_empty_source() { + let error = compile_runtime_model("", "ode", None).expect_err("empty source should fail"); + + assert_eq!(error.stage(), BridgeStage::Compile); + assert_eq!(error.code(), "model_source_empty"); + assert_eq!(error.message(), "Model source is empty"); + } + + #[test] + fn bridge_error_data_stage_tags_missing_data_path() { + let error = validate_data_path("/definitely/missing/path.csv") + .expect_err("missing path should fail"); + + assert_eq!(error.stage(), BridgeStage::Data); + assert_eq!(error.code(), "data_path_missing"); + } + + #[test] + fn bridge_error_settings_stage_tags_invalid_log_level() { + let error = parse_log_level("nope").map_err(|error| { + BridgeError::from_anyhow(BridgeStage::Settings, "log_controls_invalid", error) + }); + let error = error.expect_err("invalid log level should fail"); + + assert_eq!(error.stage(), BridgeStage::Settings); + assert_eq!(error.code(), "log_controls_invalid"); + } } extendr_module! { @@ -457,5 +705,10 @@ extendr_module! { fn fit; fn model_parameters; fn setup_logs; + fn start_live_session; + fn wait_live_session_connected; + fn close_live_session; + fn publish_live_report_result; + fn publish_live_report_failed; } diff --git a/src/rust/src/live.rs b/src/rust/src/live.rs new file mode 100644 index 000000000..1997eef80 --- /dev/null +++ b/src/rust/src/live.rs @@ -0,0 +1,495 @@ +use anyhow::{anyhow, Context, Result as AnyResult}; +use extendr_api::{list, List}; +use pmcore::api::{FitControl, FitProgress}; +use serde::{Deserialize, Serialize}; +use std::collections::{HashMap, VecDeque}; +use std::io::{BufRead, BufReader, ErrorKind, Write}; +use std::net::{Shutdown, TcpListener, TcpStream}; +use std::sync::{Arc, Mutex, OnceLock}; +use std::thread; +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; + +static LIVE_SESSIONS: OnceLock>>> = OnceLock::new(); + +fn registry() -> &'static Mutex>> { + LIVE_SESSIONS.get_or_init(|| Mutex::new(HashMap::new())) +} + +#[derive(Debug, Clone, Serialize)] +#[serde(tag = "kind", rename_all = "snake_case")] +enum LiveSessionMessage { + SessionStarted { session_id: String }, + Progress { event: FitProgress }, + FitFailed { message: String }, + FinalReportReady { + result_payload: String, + report_generated_at: String, + }, + ReportFailed { message: String }, + SessionClosed, +} + +#[derive(Debug, Deserialize)] +struct LiveCommandEnvelope { + kind: String, +} + +/// Maximum number of messages to keep in the backlog for reconnecting clients. +/// This prevents unbounded memory growth on long runs. +const MAX_BACKLOG_SIZE: usize = 1000; + +#[derive(Debug, Default)] +struct LiveSessionState { + backlog: Vec, + command_queue: VecDeque, + client_writer: Option, + connected: bool, + closed: bool, +} + +#[derive(Debug)] +pub(crate) struct LiveSession { + id: String, + host: String, + port: u16, + state: Mutex, +} + +impl LiveSession { + fn new(listener: TcpListener) -> AnyResult> { + listener + .set_nonblocking(true) + .context("failed to configure live session listener")?; + + let port = listener + .local_addr() + .context("failed to read live session listener address")? + .port(); + let session = Arc::new(Self { + id: format!( + "pm-live-{}-{}", + std::process::id(), + SystemTime::now() + .duration_since(UNIX_EPOCH) + .context("system clock is before unix epoch")? + .as_nanos() + ), + host: "127.0.0.1".to_string(), + port, + state: Mutex::new(LiveSessionState::default()), + }); + + session.publish(LiveSessionMessage::SessionStarted { + session_id: session.id.clone(), + })?; + spawn_accept_loop(Arc::clone(&session), listener); + Ok(session) + } + + fn info(&self) -> List { + list!( + session_id = self.id.clone(), + host = self.host.clone(), + port = i32::from(self.port) + ) + } + + #[cfg(test)] + pub(crate) fn id(&self) -> &str { + &self.id + } + + #[cfg(test)] + pub(crate) fn port(&self) -> u16 { + self.port + } + + #[cfg(test)] + pub(crate) fn backlog_snapshot(&self) -> Vec { + self.state + .lock() + .expect("live session state lock poisoned") + .backlog + .clone() + } + + fn publish(&self, message: LiveSessionMessage) -> AnyResult<()> { + let payload = serde_json::to_string(&message) + .context("failed to serialize live session message")?; + let mut state = self.state.lock().expect("live session state lock poisoned"); + state.backlog.push(payload.clone()); + + if let Some(writer) = state.client_writer.as_mut() { + if writeln!(writer, "{}", payload).is_err() || writer.flush().is_err() { + state.connected = false; + state.client_writer = None; + } + } + + Ok(()) + } + + pub(crate) fn publish_progress(&self, event: FitProgress) -> AnyResult<()> { + self.publish(LiveSessionMessage::Progress { event }) + } + + pub(crate) fn publish_fit_failed(&self, message: &str) -> AnyResult<()> { + self.publish(LiveSessionMessage::FitFailed { + message: message.to_string(), + }) + } + + pub(crate) fn publish_final_report_ready( + &self, + result_payload: &str, + report_generated_at: &str, + ) -> AnyResult<()> { + self.publish(LiveSessionMessage::FinalReportReady { + result_payload: result_payload.to_string(), + report_generated_at: report_generated_at.to_string(), + }) + } + + pub(crate) fn publish_report_failed(&self, message: &str) -> AnyResult<()> { + self.publish(LiveSessionMessage::ReportFailed { + message: message.to_string(), + }) + } + + pub(crate) fn next_control(&self) -> AnyResult> { + let mut state = self.state.lock().expect("live session state lock poisoned"); + Ok(state.command_queue.pop_front()) + } + + fn wait_connected(&self, timeout: Duration) -> bool { + let deadline = Instant::now() + timeout; + loop { + { + let state = self.state.lock().expect("live session state lock poisoned"); + if state.connected { + return true; + } + if state.closed { + return false; + } + } + + if Instant::now() >= deadline { + return false; + } + + thread::sleep(Duration::from_millis(25)); + } + } + + fn close(&self) -> AnyResult<()> { + self.publish(LiveSessionMessage::SessionClosed)?; + let writer = { + let mut state = self.state.lock().expect("live session state lock poisoned"); + state.closed = true; + state.client_writer.take() + }; + if let Some(writer) = writer { + let _ = writer.shutdown(Shutdown::Both); + } + Ok(()) + } +} + +fn spawn_accept_loop(session: Arc, listener: TcpListener) { + thread::spawn(move || loop { + { + let state = session.state.lock().expect("live session state lock poisoned"); + if state.closed { + break; + } + } + + match listener.accept() { + Ok((stream, _)) => { + if let Err(error) = attach_client(Arc::clone(&session), stream) { + tracing::warn!("live session client attach failed: {}", error); + } + } + Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => { + thread::sleep(Duration::from_millis(25)); + } + Err(error) => { + tracing::warn!("live session accept failed: {}", error); + break; + } + } + }); +} + +fn attach_client(session: Arc, stream: TcpStream) -> AnyResult<()> { + stream + .set_nonblocking(false) + .context("failed to configure live session client stream")?; + stream + .set_nodelay(true) + .context("failed to enable TCP_NODELAY for live session")?; + let mut writer_stream = stream + .try_clone() + .context("failed to clone live session stream")?; + let backlog = { + let state = session.state.lock().expect("live session state lock poisoned"); + state.backlog.clone() + }; + + for message in backlog { + writeln!(writer_stream, "{}", message) + .context("failed to write live session backlog")?; + writer_stream + .flush() + .context("failed to flush live session backlog")?; + } + + { + let mut state = session.state.lock().expect("live session state lock poisoned"); + state.connected = true; + state.client_writer = Some(writer_stream); + } + + let reader_session = Arc::clone(&session); + thread::spawn(move || { + let mut reader = BufReader::new(stream); + loop { + let mut line = String::new(); + match reader.read_line(&mut line) { + Ok(0) => break, + Ok(_) => { + let line = line.trim(); + if line.is_empty() { + continue; + } + + match parse_command(line) { + Ok(command) => { + let mut state = reader_session + .state + .lock() + .expect("live session state lock poisoned"); + state.command_queue.push_back(command); + } + Err(error) => { + tracing::warn!("ignoring invalid live session command '{}': {}", line, error); + } + } + } + Err(error) + if matches!( + error.kind(), + ErrorKind::WouldBlock | ErrorKind::Interrupted | ErrorKind::TimedOut + ) => + { + thread::sleep(Duration::from_millis(25)); + } + Err(error) => { + let closed = { + let state = reader_session + .state + .lock() + .expect("live session state lock poisoned"); + state.closed + }; + if !closed { + tracing::warn!("live session read failed: {}", error); + } + break; + } + } + } + }); + + Ok(()) +} + +fn parse_command(line: &str) -> AnyResult { + if let Ok(command) = serde_json::from_str::(line) { + return fit_control_from_kind(&command.kind); + } + + fit_control_from_kind(line) +} + +fn fit_control_from_kind(kind: &str) -> AnyResult { + match kind.trim().to_ascii_lowercase().as_str() { + "pause_after_cycle" => Ok(FitControl::PauseAfterCycle), + "resume" => Ok(FitControl::Resume), + "stop_after_cycle" => Ok(FitControl::StopAfterCycle), + "ping" => Ok(FitControl::Ping), + other => Err(anyhow!("unsupported live session command '{}'", other)), + } +} + +pub(crate) fn session(session_id: &str) -> AnyResult> { + registry() + .lock() + .expect("live session registry lock poisoned") + .get(session_id) + .cloned() + .ok_or_else(|| anyhow!("unknown live session '{}'", session_id)) +} + +pub(crate) fn create_live_session() -> AnyResult> { + let listener = TcpListener::bind("127.0.0.1:0").context("failed to bind live session")?; + let session = LiveSession::new(listener)?; + registry() + .lock() + .expect("live session registry lock poisoned") + .insert(session.id.clone(), Arc::clone(&session)); + Ok(session) +} + +pub(crate) fn start_live_session() -> AnyResult { + let session = create_live_session()?; + let info = session.info(); + Ok(info) +} + +pub(crate) fn wait_live_session_connected(session_id: &str, timeout_ms: i32) -> AnyResult { + let timeout_ms = timeout_ms.max(0) as u64; + Ok(session(session_id)?.wait_connected(Duration::from_millis(timeout_ms))) +} + +pub(crate) fn publish_live_report_result( + session_id: &str, + result_payload: &str, + report_generated_at: &str, +) -> AnyResult<()> { + session(session_id)?.publish_final_report_ready(result_payload, report_generated_at) +} + +pub(crate) fn publish_live_report_failed(session_id: &str, message: &str) -> AnyResult<()> { + session(session_id)?.publish_report_failed(message) +} + +pub(crate) fn close_live_session(session_id: &str) -> AnyResult<()> { + if let Some(session) = registry() + .lock() + .expect("live session registry lock poisoned") + .remove(session_id) + { + session.close()?; + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use pmcore::api::FitProgress; + use std::io::{BufRead, BufReader, Write}; + use std::net::TcpStream; + + fn connect(session: &LiveSession) -> AnyResult { + let stream = TcpStream::connect(("127.0.0.1", session.port())) + .context("failed to connect to live session")?; + stream + .set_read_timeout(Some(Duration::from_secs(2))) + .context("failed to set live session read timeout")?; + Ok(stream) + } + + #[test] + fn live_session_accepts_client_and_replays_backlog() -> AnyResult<()> { + let session = create_live_session()?; + let stream = connect(&session)?; + + let mut reader = BufReader::new(stream); + let mut first_line = String::new(); + reader + .read_line(&mut first_line) + .context("failed to read live session backlog")?; + + let message: serde_json::Value = serde_json::from_str(first_line.trim()) + .context("failed to parse live session backlog")?; + assert_eq!(message["kind"], "session_started"); + assert_eq!(message["session_id"], session.id()); + + close_live_session(session.id())?; + Ok(()) + } + + #[test] + fn live_session_publishes_progress_and_reads_commands() -> AnyResult<()> { + let session = create_live_session()?; + let mut stream = connect(&session)?; + + stream + .write_all(b"{\"kind\":\"stop_after_cycle\"}\n") + .context("failed to write live session command")?; + stream.flush().context("failed to flush live session command")?; + + let started = Instant::now(); + let mut received = None; + while started.elapsed() < Duration::from_secs(2) { + if let Some(command) = session.next_control()? { + received = Some(command); + break; + } + thread::sleep(Duration::from_millis(25)); + } + + assert!(matches!(received, Some(FitControl::StopAfterCycle))); + + session.publish_progress(FitProgress::FitStarted)?; + let state = session.state.lock().expect("live session state lock poisoned"); + assert!(state.backlog.iter().any(|line| line.contains("\"fit_started\""))); + drop(state); + + close_live_session(session.id())?; + Ok(()) + } + + #[test] + fn live_session_reads_commands_after_idle_delay() -> AnyResult<()> { + let session = create_live_session()?; + let mut stream = connect(&session)?; + + thread::sleep(Duration::from_millis(150)); + + stream + .write_all(b"{\"kind\":\"ping\"}\n") + .context("failed to write delayed live session command")?; + stream + .flush() + .context("failed to flush delayed live session command")?; + + let started = Instant::now(); + let mut received = None; + while started.elapsed() < Duration::from_secs(2) { + if let Some(command) = session.next_control()? { + received = Some(command); + break; + } + thread::sleep(Duration::from_millis(25)); + } + + assert!(matches!(received, Some(FitControl::Ping))); + + close_live_session(session.id())?; + Ok(()) + } + + #[test] + fn live_session_publishes_final_report_messages() -> AnyResult<()> { + let session = create_live_session()?; + + session.publish_final_report_ready("encoded-result", "2026-05-25T00:00:00Z")?; + session.publish_report_failed("final report handoff failed")?; + + let backlog = session.backlog_snapshot(); + assert!(backlog + .iter() + .any(|line| line.contains("\"kind\":\"final_report_ready\""))); + assert!(backlog + .iter() + .any(|line| line.contains("\"kind\":\"report_failed\""))); + + close_live_session(session.id())?; + Ok(()) + } +} \ No newline at end of file diff --git a/src/rust/src/settings.rs b/src/rust/src/settings.rs index 98543a82b..993e5b6ec 100644 --- a/src/rust/src/settings.rs +++ b/src/rust/src/settings.rs @@ -1,6 +1,10 @@ use anyhow::{anyhow, bail, Result as AnyResult}; +use crate::fit_payload::{ + FitPayloadConfig, FitPayloadErrorModel, FitPayloadErrorModels, FitPayloadParameter, + FitPayloadPoly, +}; use extendr_api::{Conversions, List, Robj}; -use pmcore::api::NonparametricEstimationProblemBuilder; +use pmcore::api::{FitControlSource, FitProgress, NonparametricEstimationProblemBuilder}; use pmcore::prelude::*; use std::collections::HashMap; @@ -132,13 +136,159 @@ impl FitBuilder where E: Equation + Clone + Send + 'static + EquationMetadataSource, { - pub(crate) fn fit(self) -> AnyResult> { + pub(crate) fn fit_in_memory(self) -> AnyResult> { match self { - Self::Npag(builder) => builder.fit(), - Self::Npod(builder) => builder.fit(), - Self::PostProb(builder) => builder.fit(), + Self::Npag(builder) => builder.fit_in_memory(), + Self::Npod(builder) => builder.fit_in_memory(), + Self::PostProb(builder) => builder.fit_in_memory(), } } + + pub(crate) fn fit_with_progress_and_control_in_memory( + self, + on_progress: F, + next_control: C, + ) -> AnyResult> + where + F: FnMut(FitProgress), + C: FitControlSource, + { + match self { + Self::Npag(builder) => { + builder.fit_with_progress_and_control_in_memory(on_progress, next_control) + } + Self::Npod(builder) => { + builder.fit_with_progress_and_control_in_memory(on_progress, next_control) + } + Self::PostProb(builder) => { + builder.fit_with_progress_and_control_in_memory(on_progress, next_control) + } + } + } +} + +pub(crate) fn live_session_id(settings: &List) -> AnyResult> { + let settings: HashMap<&str, Robj> = HashMap::try_from(settings) + .map_err(|e| anyhow!("Failed to convert settings list to map: {}", e))?; + + Ok(settings + .get("live_session_id") + .and_then(|value| value.as_str()) + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToString::to_string)) +} + +pub(crate) fn runtime_interval(settings: &List) -> AnyResult<(f64, f64)> { + let settings: HashMap<&str, Robj> = HashMap::try_from(settings) + .map_err(|e| anyhow!("Failed to convert settings list to map: {}", e))?; + + Ok(( + get_real_or(&settings, "idelta", 0.12)?, + get_real_or(&settings, "tad", 0.0)?, + )) +} + +pub(crate) fn fit_payload_config( + settings: &List, + parameter_names: &[String], +) -> AnyResult { + let settings: HashMap<&str, Robj> = HashMap::try_from(settings) + .map_err(|e| anyhow!("Failed to convert settings list to map: {}", e))?; + + let ranges = get_list(&settings, "ranges")?; + let ranges = robj_to_hashmap(ranges)?; + let parameters = parameter_names + .iter() + .map(|parameter_name| { + let (lower, upper) = ranges.get(parameter_name).copied().ok_or_else(|| { + anyhow!( + "Parameter {} not found in ranges {:?}", + parameter_name, + &ranges + ) + })?; + + Ok(FitPayloadParameter { + name: parameter_name.clone(), + lower, + upper, + }) + }) + .collect::>>()?; + + let error_models_raw = get_list(&settings, "error_models")?; + let error_models = error_models_raw + .iter() + .enumerate() + .map(|(i, (_, em))| { + let outeq = i + 1; + let em_list = em + .as_list() + .ok_or_else(|| anyhow!("error_models[{}] is not a list", outeq))?; + let em: HashMap<&str, Robj> = HashMap::try_from(&em_list) + .map_err(|e| anyhow!("Failed to parse error_models[{}]: {}", outeq, e))?; + + let type_vec = get_field(&em, "type")? + .as_string_vector() + .ok_or_else(|| anyhow!("error_models[{}].type is not a character vector", outeq))?; + let err_type = type_vec + .first() + .ok_or_else(|| anyhow!("error_models[{}].type is empty", outeq))?; + let coeff = get_field(&em, "coeff")? + .as_real_vector() + .ok_or_else(|| anyhow!("error_models[{}].coeff is not a numeric vector", outeq))?; + if coeff.len() < 4 { + bail!( + "error_models[{}].coeff must have at least 4 values, got {}", + outeq, + coeff.len() + ); + } + + let mut model = HashMap::new(); + model.insert( + error_model_name(err_type.as_str())?, + FitPayloadErrorModel { + poly: FitPayloadPoly { + c0: coeff[0], + c1: coeff[1], + c2: coeff[2], + c3: coeff[3], + }, + }, + ); + + Ok(model) + }) + .collect::>>()?; + + let ind_points = get_real_or(&settings, "points", 2028.0)?; + let prior = match get_str(&settings, "prior")?.to_ascii_lowercase().as_str() { + "sobol" => { + let mut prior = HashMap::new(); + prior.insert("Sobol".to_string(), vec![ind_points]); + Some(prior) + } + "prior.csv" => None, + other => return Err(anyhow!("Prior {} not supported", other)), + }; + + Ok(FitPayloadConfig { + parameters: vec![parameters], + errormodels: FitPayloadErrorModels { + models: error_models, + }, + prior, + }) +} + +fn error_model_name(value: &str) -> AnyResult { + match value.trim().to_ascii_lowercase().as_str() { + "additive" => Ok("Additive".to_string()), + "proportional" => Ok("Proportional".to_string()), + other => Err(anyhow!("Invalid Error type: {}", other)), + } } pub(crate) fn settings( diff --git a/tests/testthat/helper-bridge-errors.R b/tests/testthat/helper-bridge-errors.R new file mode 100644 index 000000000..5d036bee2 --- /dev/null +++ b/tests/testthat/helper-bridge-errors.R @@ -0,0 +1,32 @@ +mock_bridge_error_message <- function( + stage = "runtime", + code = "bridge_error", + message = "bridge error", + diagnostic = NULL, + details = list() +) { + if (!is.list(details)) { + details <- list() + } + + payload <- jsonlite::toJSON( + list( + kind = "error", + schema_version = 1, + stage = stage, + code = code, + message = message, + diagnostic = diagnostic, + details = details + ), + auto_unbox = TRUE, + null = "null" + ) + + rendered <- message + if (!is.null(diagnostic)) { + rendered <- paste(rendered, diagnostic, sep = "\n") + } + + paste0(rendered, "\n\nPMETRICS_BRIDGE_ERROR=", payload) +} \ No newline at end of file diff --git a/tests/testthat/test-fit-payload-result.R b/tests/testthat/test-fit-payload-result.R new file mode 100644 index 000000000..de3e3eeb6 --- /dev/null +++ b/tests/testthat/test-fit-payload-result.R @@ -0,0 +1,561 @@ +build_example_fit_params <- function(model) { + list( + ranges = list(ke = c(0.1, 1.0), v = c(1.0, 10.0)), + error_models = lapply(model$model_list$err, function(x) x$flatten()), + prior = "sobol", + points = 20 + ) +} + +build_example_fit_payload_contents <- function(include_cens = TRUE, missing_prediction_fields = character()) { + prediction <- list( + id = "1", + time = 0, + outeq = 0, + block = 0, + obs = 10, + pop_mean = 11, + pop_median = 12, + post_mean = 13, + post_median = 14 + ) + + if (include_cens) { + prediction$cens <- "none" + } + if (length(missing_prediction_fields)) { + prediction[missing_prediction_fields] <- NULL + } + + list( + parameter_names = c("ke", "v"), + covariate_names = c("cov1"), + iterations = list(list( + cycle = 1, + converged = FALSE, + status = "payload", + neg2ll = 123, + nspp = 2, + `gamlam.0` = 0.5, + `ke.mean` = 2, + `ke.median` = 2, + `ke.sd` = 0.25, + `v.mean` = 4, + `v.median` = 4, + `v.sd` = 0.5 + )), + theta = list( + list(ke = 2, v = 4, prob = 0.6), + list(ke = 3, v = 5, prob = 0.4) + ), + posterior = list( + list(id = "1", point = 1, ke = 2, v = 4, prob = 0.7), + list(id = "1", point = 2, ke = 3, v = 5, prob = 0.3) + ), + predictions = list(prediction), + covariates = list(list(id = "1", time = 0, block = 0, cov1 = 5)) + ) +} + +build_example_fit_payload_json <- function(include_cens = TRUE, missing_prediction_fields = character()) { + jsonlite::toJSON( + build_example_fit_payload_contents( + include_cens = include_cens, + missing_prediction_fields = missing_prediction_fields + ), + auto_unbox = TRUE, + null = "null" + ) +} + +build_example_fit_result_envelope_json <- function( + fit_params, + transport = c("inline", "manifest"), + include_cens = TRUE, + missing_prediction_fields = character() +) { + transport <- match.arg(transport) + payload <- build_example_fit_payload_contents( + include_cens = include_cens, + missing_prediction_fields = missing_prediction_fields + ) + build_config <- getFromNamespace("build_fit_payload_config", "Pmetrics") + + envelope <- list( + kind = "ok", + schema_version = 1, + transport = transport, + config = build_config(fit_params) + ) + + if (identical(transport, "inline")) { + envelope$payload <- payload + } else { + envelope$manifest <- list( + generator = list(name = "pm_rs", version = "0.1.0"), + tables = list( + iterations = list(path = "iterations.csv", row_count = length(payload$iterations), sha256 = "stub"), + theta = list(path = "theta.csv", row_count = length(payload$theta), sha256 = "stub"), + posterior = list(path = "posterior.csv", row_count = length(payload$posterior), sha256 = "stub"), + predictions = list(path = "predictions.csv", row_count = length(payload$predictions), sha256 = "stub"), + covariates = list(path = "covariates.csv", row_count = length(payload$covariates), sha256 = "stub") + ) + ) + } + + jsonlite::toJSON(envelope, auto_unbox = TRUE, null = "null") +} + +write_example_fit_outputs <- function(path, fit_params, include_cens = TRUE, missing_prediction_fields = character()) { + dir.create(path, recursive = TRUE, showWarnings = FALSE) + payload <- build_example_fit_payload_contents( + include_cens = include_cens, + missing_prediction_fields = missing_prediction_fields + ) + build_config <- getFromNamespace("build_fit_payload_config", "Pmetrics") + + readr::write_csv(dplyr::bind_rows(payload$theta), file.path(path, "theta.csv")) + readr::write_csv(dplyr::bind_rows(payload$posterior), file.path(path, "posterior.csv")) + readr::write_csv(dplyr::bind_rows(payload$iterations), file.path(path, "iterations.csv")) + readr::write_csv(dplyr::bind_rows(payload$predictions), file.path(path, "predictions.csv")) + readr::write_csv(dplyr::bind_rows(payload$covariates), file.path(path, "covariates.csv")) + writeLines( + jsonlite::toJSON(build_config(fit_params), auto_unbox = TRUE), + file.path(path, "settings.json") + ) +} + +write_example_fit_manifest <- function(path, fit_params, include_cens = TRUE, missing_prediction_fields = character()) { + write_example_fit_outputs( + path, + fit_params = fit_params, + include_cens = include_cens, + missing_prediction_fields = missing_prediction_fields + ) + unlink(file.path(path, "settings.json")) + writeLines( + build_example_fit_result_envelope_json( + fit_params = fit_params, + transport = "manifest", + include_cens = include_cens, + missing_prediction_fields = missing_prediction_fields + ), + file.path(path, "fit_manifest.json") + ) +} + +testthat::test_that("PM_model$fit builds PM_result without PM_parse or PM_load", { + testthat::skip_on_cran() + testthat::skip_if_not( + is_cargo_installed(), + message = "Cargo is required to compile and run PM_model$fit tests." + ) + + mod <- build_example_ode_model(compile = TRUE) + dat <- PM_data$new(data = "ex.csv", quiet = TRUE) + run_root <- withr::local_tempdir(pattern = "fit-payload-") + + original_parse <- get("PM_parse", envir = asNamespace("Pmetrics")) + original_load <- get("PM_load", envir = asNamespace("Pmetrics")) + withr::defer(assignInNamespace("PM_parse", original_parse, ns = "Pmetrics")) + withr::defer(assignInNamespace("PM_load", original_load, ns = "Pmetrics")) + + assignInNamespace( + "PM_parse", + function(...) stop("PM_parse should not be called on the active fit return path"), + ns = "Pmetrics" + ) + assignInNamespace( + "PM_load", + function(...) stop("PM_load should not be called on the active fit return path"), + ns = "Pmetrics" + ) + + res <- suppressMessages( + mod$fit( + data = dat, + path = run_root, + run = 1, + overwrite = TRUE, + cycles = 1, + points = 20, + report = "none", + quiet = TRUE + ) + ) + + testthat::expect_s3_class(res, "PM_result") + testthat::expect_s3_class(res$op, "PM_op") + testthat::expect_s3_class(res$pop, "PM_pop") + testthat::expect_s3_class(res$post, "PM_post") + testthat::expect_s3_class(res$cycle, "PM_cycle") + testthat::expect_s3_class(res$final, "PM_final") + testthat::expect_true(file.exists(file.path(run_root, "1", "outputs", "PMout.Rdata"))) +}) + +testthat::test_that("PM_result refit helpers stay stable on payload return path", { + testthat::skip_on_cran() + testthat::skip_if_not( + is_cargo_installed(), + message = "Cargo is required to compile and run PM_model$fit tests." + ) + + mod <- build_example_ode_model(compile = TRUE) + dat <- PM_data$new(data = "ex.csv", quiet = TRUE) + + base_root <- withr::local_tempdir(pattern = "fit-payload-base-") + base_result <- suppressMessages( + mod$fit( + data = dat, + path = base_root, + run = 1, + overwrite = TRUE, + cycles = 1, + points = 20, + report = "none", + quiet = TRUE + ) + ) + + refit_root <- withr::local_tempdir(pattern = "fit-payload-refit-") + refit_result <- suppressMessages( + base_result$fit( + cycles = 1, + points = 20, + path = refit_root, + run = 1, + overwrite = TRUE, + report = "none", + quiet = TRUE + ) + ) + + continue_root <- withr::local_tempdir(pattern = "fit-payload-continue-") + continue_result <- suppressMessages( + base_result$continue( + cycles = 1, + path = continue_root, + run = 1, + overwrite = TRUE, + report = "none", + quiet = TRUE + ) + ) + + testthat::expect_s3_class(refit_result, "PM_result") + testthat::expect_s3_class(continue_result, "PM_result") + testthat::expect_true(file.exists(file.path(refit_root, "1", "outputs", "PMout.Rdata"))) + testthat::expect_true(file.exists(file.path(continue_root, "1", "outputs", "PMout.Rdata"))) +}) + +testthat::test_that("build_pm_result_from_fit_payload prefers payload data over output files", { + model <- build_example_ode_model(compile = FALSE) + data <- PM_data$new(data = "ex.csv", quiet = TRUE) + out_path <- withr::local_tempdir(pattern = "fit-payload-prefer-") + + writeLines( + c( + "ke,v,prob", + "999,999,1" + ), + file.path(out_path, "theta.csv") + ) + writeLines( + c( + "id,point,ke,v,prob", + "1,1,999,999,1" + ), + file.path(out_path, "posterior.csv") + ) + writeLines( + c( + "cycle,converged,status,neg2ll,nspp,gamlam.0,ke.mean,ke.median,ke.sd,v.mean,v.median,v.sd", + "1,false,file,999,1,999,999,999,999,999,999,999" + ), + file.path(out_path, "iterations.csv") + ) + writeLines( + c( + "id,time,outeq,block,obs,cens,pop_mean,pop_median,post_mean,post_median", + "1,0,0,0,10,none,999,999,999,999" + ), + file.path(out_path, "predictions.csv") + ) + writeLines( + c( + "id,time,block,cov1", + "1,0,0,999" + ), + file.path(out_path, "covariates.csv") + ) + writeLines( + '{"parameters":[[{"name":"ke","lower":0.1,"upper":1},{"name":"v","lower":1,"upper":10}]],"errormodels":{"models":[{"Additive":{"poly":{"c0":999,"c1":0,"c2":0,"c3":0}}}]},"prior":{"Sobol":[999]}}', + file.path(out_path, "settings.json") + ) + + fit_params <- list( + ranges = list(ke = c(0.1, 1.0), v = c(1.0, 10.0)), + error_models = lapply(model$model_list$err, function(x) x$flatten()), + prior = "sobol", + points = 20 + ) + + payload_json <- build_example_fit_result_envelope_json( + fit_params = fit_params, + transport = "inline" + ) + + res <- build_pm_result_from_fit_payload( + payload_json = payload_json, + data = data, + model = model, + path = out_path + ) + + testthat::expect_equal( + unique(res$op$data$pred[res$op$data$pred.type == "pop" & res$op$data$icen == "mean"]), + 11 + ) + testthat::expect_equal( + unique(res$pop$data$pred[res$pop$data$icen == "mean"]), + 11 + ) + testthat::expect_equal( + unique(res$post$data$pred[res$post$data$icen == "mean"]), + 13 + ) + testthat::expect_equal(res$cycle$data$objective$neg2ll[[1]], 123) + testthat::expect_equal(res$final$data$popPoints$ke[[1]], 2) + testthat::expect_equal(res$final$data$gridpts, 20) +}) + +testthat::test_that("build_pm_result_from_fit_payload creates a missing output directory", { + model <- build_example_ode_model(compile = FALSE) + data <- PM_data$new(data = "ex.csv", quiet = TRUE) + out_path <- file.path(withr::local_tempdir(pattern = "fit-payload-missing-out-"), "outputs") + + testthat::expect_false(dir.exists(out_path)) + + fit_params <- list( + ranges = list(ke = c(0.1, 1.0), v = c(1.0, 10.0)), + error_models = lapply(model$model_list$err, function(x) x$flatten()), + prior = "sobol", + points = 20 + ) + + payload_json <- build_example_fit_result_envelope_json( + fit_params = fit_params, + transport = "inline" + ) + + res <- build_pm_result_from_fit_payload( + payload_json = payload_json, + data = data, + model = model, + path = out_path + ) + + testthat::expect_s3_class(res, "PM_result") + testthat::expect_true(dir.exists(out_path)) + testthat::expect_true(file.exists(file.path(out_path, "PMout.Rdata"))) +}) + +testthat::test_that("build_pm_result_from_fit_payload supports manifest transport", { + model <- build_example_ode_model(compile = FALSE) + data <- PM_data$new(data = "ex.csv", quiet = TRUE) + out_path <- withr::local_tempdir(pattern = "fit-payload-manifest-") + + fit_params <- list( + ranges = list(ke = c(0.1, 1.0), v = c(1.0, 10.0)), + error_models = lapply(model$model_list$err, function(x) x$flatten()), + prior = "sobol", + points = 20 + ) + + write_example_fit_manifest(out_path, fit_params = fit_params) + + res <- build_pm_result_from_fit_payload( + payload_json = build_example_fit_result_envelope_json( + fit_params = fit_params, + transport = "manifest" + ), + data = data, + model = model, + path = out_path + ) + + testthat::expect_s3_class(res, "PM_result") + testthat::expect_equal( + unique(res$op$data$pred[res$op$data$pred.type == "pop" & res$op$data$icen == "mean"]), + 11 + ) + testthat::expect_equal(res$final$data$gridpts, 20) + testthat::expect_true(file.exists(file.path(out_path, "fit_payload.rds"))) +}) + +testthat::test_that("decode_fit_payload errors on missing required payload columns", { + model <- build_example_ode_model(compile = FALSE) + fit_params <- build_example_fit_params(model) + + err <- rlang::catch_cnd( + decode_fit_payload( + build_example_fit_payload_json(missing_prediction_fields = "obs"), + fit_params = fit_params + ), + classes = "error" + ) + + testthat::expect_s3_class(err, "pmetrics_result_schema_missing_field_error") + testthat::expect_s3_class(err, "pmetrics_result_schema_error") + testthat::expect_equal(err$table, "predictions") + testthat::expect_equal(err$column, "obs") +}) + +testthat::test_that("fit payload normalization applies cens defaults for payload and outputs", { + model <- build_example_ode_model(compile = FALSE) + fit_params <- build_example_fit_params(model) + out_path <- withr::local_tempdir(pattern = "fit-payload-default-cens-") + + payload <- decode_fit_payload( + build_example_fit_payload_json(include_cens = FALSE), + fit_params = fit_params + ) + write_example_fit_outputs(out_path, fit_params = fit_params, include_cens = FALSE) + file_payload <- read_fit_payload_from_outputs(out_path) + + testthat::expect_equal(payload$predictions$cens, "none") + testthat::expect_equal(file_payload$predictions$cens, "none") +}) + +testthat::test_that("read_fit_payload_from_outputs errors on missing required output columns", { + model <- build_example_ode_model(compile = FALSE) + fit_params <- build_example_fit_params(model) + out_path <- withr::local_tempdir(pattern = "fit-payload-missing-output-col-") + + write_example_fit_outputs( + out_path, + fit_params = fit_params, + missing_prediction_fields = "obs" + ) + + err <- rlang::catch_cnd( + read_fit_payload_from_outputs(out_path), + classes = "error" + ) + + testthat::expect_s3_class(err, "pmetrics_result_schema_missing_field_error") + testthat::expect_s3_class(err, "pmetrics_result_schema_error") + testthat::expect_equal(err$table, "predictions") + testthat::expect_equal(err$column, "obs") +}) + +testthat::test_that("PM_model$compile rethrows structured bridge errors with compile metadata", { + model <- build_example_ode_model(compile = FALSE) + + err <- rlang::catch_cnd( + testthat::with_mocked_bindings( + validate_model_source = function(...) { + stop( + mock_bridge_error_message( + stage = "compile", + code = "runtime_compile_failed", + message = "compile failed", + diagnostic = "line 1: unexpected token" + ), + call. = FALSE + ) + }, + { + model$compile(quiet = TRUE) + } + ), + classes = "error" + ) + + testthat::expect_s3_class(err, "pmetrics_bridge_compile_error") + testthat::expect_equal(err$stage, "compile") + testthat::expect_equal(err$code, "runtime_compile_failed") + testthat::expect_equal(err$diagnostic, "line 1: unexpected token") + testthat::expect_match(conditionMessage(err), "compile failed") + testthat::expect_match(conditionMessage(err), "line 1: unexpected token") +}) + +testthat::test_that("PM_model$fit rethrows runtime bridge failures instead of returning NULL", { + model <- build_example_ode_model(compile = FALSE) + data <- PM_data$new(data = "ex.csv", quiet = TRUE) + run_root <- withr::local_tempdir(pattern = "fit-bridge-runtime-") + + err <- rlang::catch_cnd( + testthat::with_mocked_bindings( + validate_model_source = function(...) invisible(NULL), + fit = function(...) { + stop( + mock_bridge_error_message( + stage = "runtime", + code = "fit_execution_failed", + message = "fit failed", + details = list(algorithm = "npag") + ), + call. = FALSE + ) + }, + { + suppressMessages( + model$fit( + data = data, + path = run_root, + run = 1, + overwrite = TRUE, + cycles = 1, + points = 20, + report = "none", + quiet = TRUE + ) + ) + } + ), + classes = "error" + ) + + testthat::expect_s3_class(err, "pmetrics_bridge_runtime_error") + testthat::expect_equal(err$stage, "runtime") + testthat::expect_equal(err$code, "fit_execution_failed") + testthat::expect_equal(err$details$algorithm, "npag") + testthat::expect_false(inherits(err, "pmetrics_bridge_unstructured_error")) +}) + +testthat::test_that("PM_model$fit keeps compatibility with unstructured bridge failures", { + model <- build_example_ode_model(compile = FALSE) + data <- PM_data$new(data = "ex.csv", quiet = TRUE) + run_root <- withr::local_tempdir(pattern = "fit-bridge-unstructured-") + + err <- rlang::catch_cnd( + testthat::with_mocked_bindings( + validate_model_source = function(...) invisible(NULL), + fit = function(...) { + stop("old flat string bridge failure", call. = FALSE) + }, + { + suppressMessages( + model$fit( + data = data, + path = run_root, + run = 1, + overwrite = TRUE, + cycles = 1, + points = 20, + report = "none", + quiet = TRUE + ) + ) + } + ), + classes = "error" + ) + + testthat::expect_s3_class(err, "pmetrics_bridge_runtime_error") + testthat::expect_s3_class(err, "pmetrics_bridge_unstructured_error") + testthat::expect_equal(err$stage, "runtime") + testthat::expect_equal(err$code, "fit_execution_failed") + testthat::expect_match(conditionMessage(err), "old flat string bridge failure") +}) diff --git a/tests/testthat/test-live-report-bridge.R b/tests/testthat/test-live-report-bridge.R new file mode 100644 index 000000000..9d3f3c4cd --- /dev/null +++ b/tests/testthat/test-live-report-bridge.R @@ -0,0 +1,130 @@ +testthat::skip_on_cran() + +testthat::skip_if_not( + is_cargo_installed(), + message = "Cargo is required to run live report bridge tests." +) + +testthat::skip_if_not_installed("pkgload") + +testthat::test_that("live report session launches app and connects before fit", { + reports_path <- testthat::test_path("..", "..", "..", "Pmetricsreports") + pkgload::load_all(reports_path, quiet = TRUE, export_all = FALSE) + + start_live <- getFromNamespace("start_live_report_session", "Pmetrics") + close_live <- getFromNamespace("close_live_report_session", "Pmetrics") + + live_session <- start_live(show = FALSE, timeout_ms = 5000L) + + withr::defer({ + if (!is.null(live_session$process)) { + try(live_session$process$kill(), silent = TRUE) + } + close_live(live_session) + }) + + testthat::expect_type(live_session, "list") + testthat::expect_true(is.list(live_session$session)) + testthat::expect_true(nzchar(live_session$session$session_id)) + testthat::expect_true(isTRUE(live_session$connected)) +}) + +testthat::test_that("live report close leaves time for finished report handoff", { + reports_path <- testthat::test_path("..", "..", "..", "Pmetricsreports") + pkgload::load_all(reports_path, quiet = TRUE, export_all = FALSE) + + start_raw_live <- getFromNamespace("start_live_session", "Pmetrics") + close_live <- getFromNamespace("close_live_report_session", "Pmetrics") + send_result <- getFromNamespace("send_live_report_result", "Pmetrics") + open_connection <- getFromNamespace("open_live_session_connection", "PmetricsReports") + close_connection <- getFromNamespace("close_live_session_connection", "PmetricsReports") + read_messages <- getFromNamespace("read_live_session_messages", "PmetricsReports") + + live_session <- list( + session = start_raw_live(), + close_grace_seconds = 0.5 + ) + connection <- open_connection(live_session$session) + session_closed <- FALSE + + withr::defer({ + close_connection(connection) + if (!session_closed) { + close_live(live_session) + } + }) + + send_result( + live_session, + structure(list(marker = paste(rep("x", 100000), collapse = "")), class = "PM_result") + ) + close_live(live_session) + session_closed <- TRUE + + messages <- list() + deadline <- Sys.time() + 1 + while (Sys.time() < deadline) { + batch <- read_messages(connection, max_messages = 50L) + if (length(batch)) { + messages <- c(messages, batch) + } + + kinds <- vapply(messages, function(message) { + if (is.null(message$kind) || !length(message$kind)) { + "" + } else { + as.character(message$kind[[1]]) + } + }, character(1)) + + if ("session_closed" %in% kinds) { + break + } + + Sys.sleep(0.05) + } + + kinds <- vapply(messages, function(message) { + if (is.null(message$kind) || !length(message$kind)) { + "" + } else { + as.character(message$kind[[1]]) + } + }, character(1)) + + testthat::expect_true("final_report_ready" %in% kinds) + testthat::expect_true("session_closed" %in% kinds) +}) + +testthat::test_that("send_live_report_result rethrows structured handoff errors", { + send_result <- getFromNamespace("send_live_report_result", "Pmetrics") + original_publish <- get("publish_live_report_result", envir = asNamespace("Pmetrics")) + + withr::defer(assignInNamespace("publish_live_report_result", original_publish, ns = "Pmetrics")) + assignInNamespace( + "publish_live_report_result", + function(...) { + stop( + mock_bridge_error_message( + stage = "handoff", + code = "live_report_publish_failed", + message = "publish failed" + ), + call. = FALSE + ) + }, + ns = "Pmetrics" + ) + + err <- rlang::catch_cnd( + send_result( + list(session = list(session_id = "pm-live-test")), + structure(list(marker = "ready"), class = "PM_result") + ), + classes = "error" + ) + + testthat::expect_s3_class(err, "pmetrics_bridge_handoff_error") + testthat::expect_equal(err$stage, "handoff") + testthat::expect_equal(err$code, "live_report_publish_failed") +}) diff --git a/tests/testthat/test-live-report-handoff.R b/tests/testthat/test-live-report-handoff.R new file mode 100644 index 000000000..f4f30b488 --- /dev/null +++ b/tests/testthat/test-live-report-handoff.R @@ -0,0 +1,166 @@ +testthat::test_that("send_live_report_result serializes and publishes the finished report payload", { + session <- list(session = list(session_id = "pm-live-test")) + result <- structure(list(marker = "ready"), class = "PM_result") + published <- NULL + + testthat::with_mocked_bindings( + publish_live_report_result = function(session_id, result_payload, report_generated_at) { + published <<- list( + session_id = session_id, + result_payload = result_payload, + report_generated_at = report_generated_at + ) + invisible(NULL) + }, + { + send_live_report_result( + live_report_session = session, + res = result, + generated_at = as.POSIXct("2026-05-25 12:34:56", tz = "UTC") + ) + } + ) + + decoded <- unserialize(memDecompress(jsonlite::base64_dec(published$result_payload), type = "gzip")) + + testthat::expect_equal(published$session_id, "pm-live-test") + testthat::expect_match(published$report_generated_at, "^2026-05-25T12:34:56") + testthat::expect_s3_class(decoded, "PM_result") + testthat::expect_equal(decoded$marker, "ready") +}) + +testthat::test_that("send_live_report_failure publishes the handoff error message", { + session <- list(session = list(session_id = "pm-live-test")) + published <- NULL + + testthat::with_mocked_bindings( + publish_live_report_failed = function(session_id, message) { + published <<- list(session_id = session_id, message = message) + invisible(NULL) + }, + { + send_live_report_failure(session, "Finished report handoff failed") + } + ) + + testthat::expect_equal(published$session_id, "pm-live-test") + testthat::expect_equal(published$message, "Finished report handoff failed") +}) + +testthat::test_that("PM_model$fit publishes the finished report to the live app when report = 'app'", { + model <- build_example_ode_model(compile = FALSE) + data <- PM_data$new(data = "ex.csv", quiet = TRUE) + run_root <- withr::local_tempdir(pattern = "live-handoff-fit-") + + fake_result <- structure(list(marker = "ready"), class = "PM_result") + sent_result <- NULL + sent_failure <- NULL + + result <- testthat::with_mocked_bindings( + validate_model_source = function(...) invisible(NULL), + start_live_report_session = function(show = TRUE, timeout_ms = 3000L) { + list( + session = list(session_id = "pm-live-test"), + process = structure(list(), class = "process"), + connected = TRUE, + url = "http://127.0.0.1:1234" + ) + }, + close_live_report_session = function(...) invisible(TRUE), + fit = function(model_source, data, params, output_path, kind, solver = NULL) { + "{\"ok\":true}" + }, + build_pm_result_from_fit_payload = function(...) fake_result, + send_live_report_result = function(live_report_session, res, generated_at = Sys.time()) { + sent_result <<- list(session = live_report_session, res = res) + invisible(TRUE) + }, + send_live_report_failure = function(live_report_session, message) { + sent_failure <<- list(session = live_report_session, message = message) + invisible(TRUE) + }, + { + suppressWarnings(suppressMessages( + model$fit( + data = data, + path = run_root, + run = 1, + overwrite = TRUE, + cycles = 1, + points = 20, + report = "app", + quiet = TRUE + ) + )) + } + ) + + testthat::expect_s3_class(result, "PM_result") + testthat::expect_null(sent_failure) + testthat::expect_true(is.list(sent_result)) + testthat::expect_equal(sent_result$session$session$session_id, "pm-live-test") + testthat::expect_identical(sent_result$res, fake_result) +}) + +testthat::test_that("PM_model$fit reports a final handoff failure to the live app", { + model <- build_example_ode_model(compile = FALSE) + data <- PM_data$new(data = "ex.csv", quiet = TRUE) + run_root <- withr::local_tempdir(pattern = "live-handoff-fail-") + + sent_result <- NULL + sent_failure <- NULL + + err <- rlang::catch_cnd( + testthat::with_mocked_bindings( + validate_model_source = function(...) invisible(NULL), + start_live_report_session = function(show = TRUE, timeout_ms = 3000L) { + list( + session = list(session_id = "pm-live-test"), + process = structure(list(), class = "process"), + connected = TRUE, + url = "http://127.0.0.1:1234" + ) + }, + close_live_report_session = function(...) invisible(TRUE), + fit = function(model_source, data, params, output_path, kind, solver = NULL) { + "{\"ok\":true}" + }, + build_pm_result_from_fit_payload = function(...) { + stop("decode failed", call. = FALSE) + }, + send_live_report_result = function(live_report_session, res, generated_at = Sys.time()) { + sent_result <<- list(session = live_report_session, res = res) + invisible(TRUE) + }, + send_live_report_failure = function(live_report_session, message) { + sent_failure <<- list(session = live_report_session, message = message) + invisible(TRUE) + }, + { + suppressMessages( + model$fit( + data = data, + path = run_root, + run = 1, + overwrite = TRUE, + cycles = 1, + points = 20, + report = "app", + quiet = TRUE + ) + ) + } + ), + classes = "error" + ) + + testthat::expect_s3_class(err, "pmetrics_bridge_handoff_error") + testthat::expect_equal(err$stage, "handoff") + testthat::expect_equal(err$code, "fit_result_build_failed") + testthat::expect_match(conditionMessage(err), "Failed to build PM_result from fit payload") + testthat::expect_match(conditionMessage(err), "decode failed") + testthat::expect_null(sent_result) + testthat::expect_true(is.list(sent_failure)) + testthat::expect_equal(sent_failure$session$session$session_id, "pm-live-test") + testthat::expect_match(sent_failure$message, "Failed to build PM_result from fit payload") +}) diff --git a/tests/testthat/test-pm-load-snapshot-fallback.R b/tests/testthat/test-pm-load-snapshot-fallback.R new file mode 100644 index 000000000..6df3b69e2 --- /dev/null +++ b/tests/testthat/test-pm-load-snapshot-fallback.R @@ -0,0 +1,193 @@ +library(Pmetrics) + +write_manifest_fixture <- function(path, model) { + fit_params <- list( + ranges = list(ke = c(0.1, 1.0), v = c(1.0, 10.0)), + error_models = lapply(model$model_list$err, function(x) x$flatten()), + prior = "sobol", + points = 20 + ) + payload <- list( + iterations = list(list( + cycle = 1, + converged = FALSE, + status = "payload", + neg2ll = 123, + nspp = 2, + `gamlam.0` = 0.5, + `ke.mean` = 2, + `ke.median` = 2, + `ke.sd` = 0.25, + `v.mean` = 4, + `v.median` = 4, + `v.sd` = 0.5 + )), + theta = list( + list(ke = 2, v = 4, prob = 0.6), + list(ke = 3, v = 5, prob = 0.4) + ), + posterior = list( + list(id = "1", point = 1, ke = 2, v = 4, prob = 0.7), + list(id = "1", point = 2, ke = 3, v = 5, prob = 0.3) + ), + predictions = list(list( + id = "1", + time = 0, + outeq = 0, + block = 0, + obs = 10, + cens = "none", + pop_mean = 11, + pop_median = 12, + post_mean = 13, + post_median = 14 + )), + covariates = list(list(id = "1", time = 0, block = 0, cov1 = 5)) + ) + build_config <- getFromNamespace("build_fit_payload_config", "Pmetrics") + + readr::write_csv(dplyr::bind_rows(payload$theta), file.path(path, "theta.csv")) + readr::write_csv(dplyr::bind_rows(payload$posterior), file.path(path, "posterior.csv")) + readr::write_csv(dplyr::bind_rows(payload$iterations), file.path(path, "iterations.csv")) + readr::write_csv(dplyr::bind_rows(payload$predictions), file.path(path, "predictions.csv")) + readr::write_csv(dplyr::bind_rows(payload$covariates), file.path(path, "covariates.csv")) + writeLines( + jsonlite::toJSON( + list( + kind = "ok", + schema_version = 1, + transport = "manifest", + config = build_config(fit_params), + manifest = list( + generator = list(name = "pm_rs", version = "0.1.0"), + tables = list( + iterations = list(path = "iterations.csv", row_count = 1, sha256 = "stub"), + theta = list(path = "theta.csv", row_count = 2, sha256 = "stub"), + posterior = list(path = "posterior.csv", row_count = 2, sha256 = "stub"), + predictions = list(path = "predictions.csv", row_count = 1, sha256 = "stub"), + covariates = list(path = "covariates.csv", row_count = 1, sha256 = "stub") + ) + ) + ), + auto_unbox = TRUE, + null = "null" + ), + file.path(path, "fit_manifest.json") + ) +} + +test_that("PM_load rebuilds runs when PMout is missing", { + run_root <- tempfile("pmetrics-load-fallback-") + dir.create(run_root, recursive = TRUE) + + model_local <- PM_model$new(x = modEx, compile = FALSE) + run_initial <- suppressMessages( + model_local$fit( + data = dataEx, + path = run_root, + run = 1, + overwrite = TRUE, + report = "none", + quiet = TRUE + ) + ) + + expect_true(inherits(run_initial, "PM_result")) + + outputs_dir <- file.path(run_root, "1", "outputs") + inputs_dir <- file.path(run_root, "1", "inputs") + expect_true(file.exists(file.path(outputs_dir, "fit_payload.rds"))) + unlink(file.path(outputs_dir, "PMout.Rdata")) + + expect_false(file.exists(file.path(outputs_dir, "PMout.Rdata"))) + expect_true(file.exists(file.path(inputs_dir, "fit.rds"))) + + expect_no_warning(loaded <- PM_load(path = run_root, run = 1)) + expect_true(inherits(loaded, "PM_result")) + expect_s3_class(loaded$data, "PM_data") + expect_s3_class(loaded$model, "PM_model") + expect_s3_class(loaded$op, "PM_op") + expect_equal(as.data.frame(loaded$op$data), as.data.frame(run_initial$op$data)) + expect_equal(loaded$final$data$popPoints, run_initial$final$data$popPoints) + expect_equal(loaded$cycle$data$objective, run_initial$cycle$data$objective) +}) + +test_that("PM_model$fit rereads an existing run without PMout snapshot", { + run_root <- tempfile("pmetrics-fit-fallback-") + dir.create(run_root, recursive = TRUE) + + model_local <- PM_model$new(x = modEx, compile = FALSE) + run_initial <- suppressMessages( + model_local$fit( + data = dataEx, + path = run_root, + run = 1, + overwrite = TRUE, + report = "none", + quiet = TRUE + ) + ) + + expect_true(inherits(run_initial, "PM_result")) + + outputs_dir <- file.path(run_root, "1", "outputs") + inputs_dir <- file.path(run_root, "1", "inputs") + expect_true(file.exists(file.path(outputs_dir, "fit_payload.rds"))) + unlink(file.path(outputs_dir, "PMout.Rdata")) + + expect_false(file.exists(file.path(outputs_dir, "PMout.Rdata"))) + expect_true(file.exists(file.path(inputs_dir, "fit.rds"))) + + reopened <- suppressMessages( + model_local$fit( + data = dataEx, + path = run_root, + run = 1, + overwrite = FALSE, + report = "none", + quiet = TRUE + ) + ) + + expect_true(inherits(reopened, "PM_result")) + expect_s3_class(reopened$op, "PM_op") + expect_s3_class(reopened$final, "PM_final") + expect_equal(as.data.frame(reopened$op$data), as.data.frame(run_initial$op$data)) +}) + +test_that("PM_load rebuilds runs from fit manifest when snapshots are missing", { + run_root <- tempfile("pmetrics-load-manifest-") + dir.create(run_root, recursive = TRUE) + + model_local <- build_example_ode_model(compile = FALSE) + data_local <- PM_data$new(data = "ex.csv", quiet = TRUE) + suppressMessages( + model_local$fit( + data = data_local, + path = run_root, + run = 1, + overwrite = TRUE, + report = "none", + quiet = TRUE + ) + ) + + outputs_dir <- file.path(run_root, "1", "outputs") + expect_true(file.exists(file.path(outputs_dir, "fit_payload.rds"))) + + unlink(file.path(outputs_dir, "PMout.Rdata")) + unlink(file.path(outputs_dir, "fit_payload.rds")) + write_manifest_fixture(outputs_dir, model_local) + + expect_false(file.exists(file.path(outputs_dir, "PMout.Rdata"))) + expect_false(file.exists(file.path(outputs_dir, "fit_payload.rds"))) + expect_true(file.exists(file.path(outputs_dir, "fit_manifest.json"))) + + expect_no_warning(loaded <- PM_load(path = run_root, run = 1)) + expect_true(inherits(loaded, "PM_result")) + expect_equal( + unique(loaded$op$data$pred[loaded$op$data$pred.type == "pop" & loaded$op$data$icen == "mean"]), + 11 + ) + expect_equal(loaded$final$data$gridpts, 20) +}) \ No newline at end of file diff --git a/tests/testthat/test-pm-report.R b/tests/testthat/test-pm-report.R new file mode 100644 index 000000000..9eacca841 --- /dev/null +++ b/tests/testthat/test-pm-report.R @@ -0,0 +1,94 @@ +testthat::test_that("PM_report respects the configured HTML report mode", { + testthat::skip_if_not_installed("rmarkdown") + + res <- structure( + list( + final = list(data = TRUE), + op = list(data = TRUE), + cycle = list(data = TRUE) + ), + class = "PM_result" + ) + + app_called <- FALSE + out_dir <- withr::local_tempdir(pattern = "pm-report-html-") + + original_render <- get("render", envir = asNamespace("rmarkdown")) + withr::defer(assignInNamespace("render", original_render, ns = "rmarkdown")) + + assignInNamespace( + "render", + function(input, output_file, params, clean, quiet) { + dir.create(dirname(output_file), recursive = TRUE, showWarnings = FALSE) + file.create(output_file) + invisible(output_file) + }, + ns = "rmarkdown" + ) + + report_status <- testthat::with_mocked_bindings( + getPMoptions = function(opt, warn = TRUE, quiet = FALSE) { + if (identical(opt, "report_template")) { + return("plotly") + } + NULL + }, + getExportedValue = function(...) { + app_called <<- TRUE + function(...) invisible(NULL) + }, + requireNamespace = function(package, quietly = TRUE) TRUE, + { + suppressMessages(PM_report(res, path = out_dir, show = FALSE, quiet = TRUE)) + } + ) + + testthat::expect_false(app_called) + testthat::expect_equal(report_status, 1) + testthat::expect_true(file.exists(file.path(out_dir, "report.html"))) +}) + +testthat::test_that("PM_report launches the app when report mode is app", { + res <- structure( + list( + final = list(data = TRUE), + op = list(data = TRUE), + cycle = list(data = TRUE) + ), + class = "PM_result" + ) + + launched <- NULL + report_status <- NULL + + testthat::expect_message( + report_status <- testthat::with_mocked_bindings( + getPMoptions = function(opt, warn = TRUE, quiet = FALSE) { + if (identical(opt, "report_template")) { + return("app") + } + NULL + }, + requireNamespace = function(package, quietly = TRUE) TRUE, + getExportedValue = function(pkg, name) { + testthat::expect_equal(pkg, "PmetricsReports") + testthat::expect_equal(name, "run_app") + function(res, launch.browser) { + launched <<- list(res = res, launch.browser = launch.browser) + process <- structure(list(), class = "process") + attr(process, "app_url") <- "http://127.0.0.1:1234" + process + } + }, + { + PM_report(res, show = TRUE, quiet = TRUE) + } + ), + "Reporting app URL" + ) + + testthat::expect_equal(report_status, 1) + testthat::expect_true(is.list(launched)) + testthat::expect_identical(launched$res, res) + testthat::expect_true(isTRUE(launched$launch.browser)) +}) From 363b4ccae0d5bdef350d968145d52e0e4e041842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20D=2E=20Ot=C3=A1lvaro?= Date: Thu, 28 May 2026 01:24:55 +0100 Subject: [PATCH 4/7] should work --- Cargo.lock | 109 +++++++++++++++++++++++++++++--------------- Cargo.toml | 4 +- src/rust/Cargo.toml | 2 +- 3 files changed, 74 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3e7a7a087..59f99e41c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,9 +94,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "bitflags" @@ -121,9 +121,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" dependencies = [ "allocator-api2", ] @@ -156,9 +156,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.60" +version = "1.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" dependencies = [ "find-msvc-tools", "shlex", @@ -571,9 +571,9 @@ checksum = "e1d926b4d407d372f141f93bb444696142c29d32962ccbd3531117cf3aa0bfa9" [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "enum-as-inner" @@ -762,6 +762,30 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[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 = "gemm" version = "0.19.0" @@ -883,9 +907,9 @@ dependencies = [ [[package]] name = "generativity" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5881e4c3c2433fe4905bb19cfd2b5d49d4248274862b68c27c33d9ba4e13f9ec" +checksum = "d2c81fb5260e37854d09d5c87183309fd8c555b75289427884b25660bc87a85e" [[package]] name = "generator" @@ -1102,9 +1126,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" @@ -1131,7 +1155,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", ] @@ -1155,10 +1179,12 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.95" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -1177,9 +1203,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" @@ -1198,9 +1224,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "loom" @@ -1245,9 +1271,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "moka" @@ -1469,9 +1495,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -1681,6 +1707,7 @@ dependencies = [ [[package]] name = "pmcore" version = "0.25.2" +source = "git+https://github.com/LAPKB/PMcore?rev=a7b2ecaa50838730e7dfc823c13812aa57263dd5#a7b2ecaa50838730e7dfc823c13812aa57263dd5" dependencies = [ "anyhow", "argmin", @@ -2018,7 +2045,7 @@ checksum = "de2c52737737f8609e94f975dee22854a2d5c125772d4b1cf292120f4d45c186" dependencies = [ "allocator-api2", "bumpalo", - "hashbrown 0.17.0", + "hashbrown 0.17.1", "log", "rustc-hash", "smallvec", @@ -2145,9 +2172,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -2195,6 +2222,12 @@ dependencies = [ "wide", ] +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + [[package]] name = "smallvec" version = "1.15.1" @@ -2511,9 +2544,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.118" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -2524,9 +2557,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.118" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2534,9 +2567,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.118" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", @@ -2547,9 +2580,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.118" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] @@ -2889,18 +2922,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "bce33a6288fa3f072a8c2c7d0f2fdbb90e28298f0135c1f99b96c3db2efcc60b" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "8fd425244944f4ab65ccff928e7323354c5a018c75838362fdce749dfad2ee1e" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index a74884bb3..9f970f0ed 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,5 +15,5 @@ exclude = [ [profile.release] panic = "unwind" -[patch."https://github.com/LAPKB/PMcore"] -pmcore = { path = "../PMcore-structure" } +# [patch."https://github.com/LAPKB/PMcore"] +# pmcore = { path = "../PMcore-structure" } diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index bbe7d7c97..af8e00d09 100755 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -9,7 +9,7 @@ name = 'pm_rs' [dependencies] extendr-api = "=0.9.0" -pmcore = { version = "=0.25.2", git = "https://github.com/LAPKB/PMcore", rev = "668dd305436c75d4fdef29138261540c114c0c34", features = ["dsl-jit"] } +pmcore = { version = "=0.25.2", git = "https://github.com/LAPKB/PMcore", rev = "a7b2ecaa50838730e7dfc823c13812aa57263dd5", features = ["dsl-jit"] } rayon = "1.10.0" anyhow = "1.0.97" From 8c084a83abad12d91eaaec2992a4cf6db362c8f2 Mon Sep 17 00:00:00 2001 From: Michael Neely Date: Fri, 29 May 2026 21:34:48 -0700 Subject: [PATCH 5/7] wip: absorbed live status into Pmetrics --- DESCRIPTION | 2 + R/PM_live.R | 138 ++- R/PM_live_app.R | 1166 ++++++++++++++++++++++++++ R/PM_model.R | 93 +- R/PM_report.R | 20 +- inst/apps/live-status/app.R | 4 + inst/apps/live-status/www/custom.css | 179 ++++ man/close_live_session.Rd | 18 + man/load_pm_parse_fit_object.Rd | 38 + man/publish_live_report_failed.Rd | 20 + man/publish_live_report_result.Rd | 22 + man/start_live_session.Rd | 15 + man/wait_live_session_connected.Rd | 20 + 13 files changed, 1652 insertions(+), 83 deletions(-) create mode 100644 R/PM_live_app.R create mode 100644 inst/apps/live-status/app.R create mode 100644 inst/apps/live-status/www/custom.css create mode 100644 man/close_live_session.Rd create mode 100644 man/load_pm_parse_fit_object.Rd create mode 100644 man/publish_live_report_failed.Rd create mode 100644 man/publish_live_report_result.Rd create mode 100644 man/start_live_session.Rd create mode 100644 man/wait_live_session_connected.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 65e7c3098..8c62c4d52 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -28,6 +28,7 @@ Depends: R (>= 4.2) Imports: bslib, + callr, cli, clipr, crayon, @@ -40,6 +41,7 @@ Imports: grid, htmltools, htmlwidgets, + httpuv, jsonlite, knitr, lifecycle, diff --git a/R/PM_live.R b/R/PM_live.R index 0be99e141..2774f8022 100644 --- a/R/PM_live.R +++ b/R/PM_live.R @@ -1,44 +1,124 @@ # nolint start -resolve_pmetrics_reports_run_app <- function() { - pmetrics_path <- tryCatch( +pmetrics_live_app_dir <- function() { + installed <- system.file("apps", "live-status", package = "Pmetrics") + if (nzchar(installed) && file.exists(file.path(installed, "app.R"))) { + return(installed) + } + + pkg_path <- tryCatch( getNamespaceInfo(asNamespace("Pmetrics"), "path"), error = function(e) "" ) - sibling_reports_path <- normalizePath( - file.path(dirname(pmetrics_path), "Pmetricsreports"), - mustWork = FALSE + + if (!nzchar(pkg_path)) { + return("") + } + + candidates <- unique(c( + file.path(pkg_path, "inst", "apps", "live-status"), + file.path(pkg_path, "inst", "apps", "live-report"), + file.path(pkg_path, "inst", "apps", "live_report") + )) + + candidates <- normalizePath(candidates, mustWork = FALSE) + found <- candidates[file.exists(file.path(candidates, "app.R"))] + if (!length(found)) { + return("") + } + + found[[1]] +} + +launch_live_report_app <- function(live_session, launch.browser = TRUE, timeout_seconds = 30) { + app_dir <- pmetrics_live_app_dir() + if (!nzchar(app_dir)) { + cli::cli_abort(c( + "x" = "The internal live reporting app could not be found.", + "i" = "Expected app files under {.path inst/apps/live-status}." + )) + } + + launch_dir <- tempfile("Pmetrics-live-status-") + dir.create(launch_dir, recursive = TRUE, showWarnings = FALSE) + + port_file <- file.path(launch_dir, "port.txt") + pkg_path <- tryCatch( + getNamespaceInfo(asNamespace("Pmetrics"), "path"), + error = function(e) "" ) - if (nzchar(sibling_reports_path) && - file.exists(file.path(sibling_reports_path, "DESCRIPTION")) && - requireNamespace("pkgload", quietly = TRUE)) { - loaded <- tryCatch( - { - pkgload::load_all(sibling_reports_path, quiet = TRUE, export_all = FALSE) - TRUE - }, - error = function(e) FALSE + process <- callr::r_bg( + function(pkg_path, app_dir, port_file, live_session) { + if (nzchar(pkg_path) && file.exists(file.path(pkg_path, "DESCRIPTION")) && requireNamespace("pkgload", quietly = TRUE)) { + pkgload::load_all(pkg_path, quiet = TRUE, export_all = TRUE) + } else { + library(Pmetrics) + } + + options(Pmetrics.live_session = unclass(live_session)) + + port <- httpuv::randomPort() + writeLines(as.character(port), port_file) + + shiny::runApp( + appDir = app_dir, + launch.browser = FALSE, + port = port, + host = "127.0.0.1" + ) + }, + args = list( + pkg_path = pkg_path, + app_dir = app_dir, + port_file = port_file, + live_session = live_session + ), + supervise = TRUE, + stdout = "|", + stderr = "|" + ) + + deadline <- Sys.time() + as.numeric(timeout_seconds) + while (!file.exists(port_file) && process$is_alive() && Sys.time() < deadline) { + Sys.sleep(0.05) + } + + if (!process$is_alive()) { + startup_output <- c( + tryCatch(process$read_all_error_lines(), error = function(e) character()), + tryCatch(process$read_all_output_lines(), error = function(e) character()) ) + startup_output <- startup_output[nzchar(startup_output)] + cli::cli_abort(c( + "x" = "Pmetrics live reporting app exited before startup completed.", + if (length(startup_output)) paste(startup_output, collapse = "\n") + )) + } - if (isTRUE(loaded)) { - return(getExportedValue("PmetricsReports", "run_app")) - } + port <- if (file.exists(port_file)) { + suppressWarnings(as.integer(readLines(port_file, warn = FALSE)[1])) + } else { + NA_integer_ } - if (!requireNamespace("PmetricsReports", quietly = TRUE)) { - return(NULL) + if (!is.finite(port) || is.na(port)) { + cli::cli_abort(c( + "x" = "Pmetrics live reporting app did not report a local URL before startup timed out." + )) } - getExportedValue("PmetricsReports", "run_app") + app_url <- sprintf("http://127.0.0.1:%s", port) + if (isTRUE(launch.browser)) { + utils::browseURL(app_url) + } + + attr(process, "app_url") <- app_url + attr(process, "launch_dir") <- launch_dir + invisible(process) } start_live_report_session <- function(show = TRUE, timeout_ms = 10000L) { - run_app <- resolve_pmetrics_reports_run_app() - if (is.null(run_app)) { - return(NULL) - } - live_session <- call_pm_bridge( start_live_session(), default_stage = "handoff", @@ -47,11 +127,9 @@ start_live_report_session <- function(show = TRUE, timeout_ms = 10000L) { process <- tryCatch( { - run_app( - res = NULL, - launch.browser = show, - background = TRUE, - live_session = live_session + launch_live_report_app( + live_session = live_session, + launch.browser = show ) }, error = function(e) { diff --git a/R/PM_live_app.R b/R/PM_live_app.R new file mode 100644 index 000000000..464fdbe2e --- /dev/null +++ b/R/PM_live_app.R @@ -0,0 +1,1166 @@ +pmetrics_live_session_config <- function() { + config <- getOption("Pmetrics.live_session", NULL) + if (!is.list(config)) { + return(NULL) + } + + session_id <- config$session_id + host <- config$host + port <- suppressWarnings(as.integer(config$port[[1]])) + + if (is.null(session_id) || !length(session_id) || !nzchar(session_id[[1]])) { + return(NULL) + } + + if (is.null(host) || !length(host) || !nzchar(host[[1]])) { + return(NULL) + } + + if (is.na(port) || port <= 0) { + return(NULL) + } + + list( + session_id = as.character(session_id[[1]]), + host = as.character(host[[1]]), + port = port + ) +} + +pmetrics_live_default <- function(value, default) { + if (is.null(value) || !length(value)) { + default + } else { + value + } +} + +pmetrics_same_live_session_config <- function(x, y) { + is.list(x) && + is.list(y) && + identical(x$session_id, y$session_id) && + identical(x$host, y$host) && + identical(as.integer(x$port), as.integer(y$port)) +} + +pmetrics_live_session_runtime <- local({ + runtime <- new.env(parent = emptyenv()) + runtime$config <- NULL + runtime$connection <- NULL + runtime$state <- NULL + runtime +}) + +pmetrics_get_live_session_state <- function(config) { + if (is.null(config)) { + return(NULL) + } + + if (!pmetrics_same_live_session_config(pmetrics_live_session_runtime$config, config) || is.null(pmetrics_live_session_runtime$state)) { + pmetrics_live_session_runtime$config <- config + pmetrics_live_session_runtime$state <- pmetrics_new_live_session_state(config) + } + + pmetrics_live_session_runtime$state +} + +pmetrics_set_live_session_state <- function(config, state) { + pmetrics_live_session_runtime$config <- config + pmetrics_live_session_runtime$state <- state + invisible(state) +} + +pmetrics_open_live_session_connection <- function(config) { + socketConnection( + host = config$host, + port = config$port, + blocking = FALSE, + open = "r+", + timeout = 1 + ) +} + +pmetrics_close_live_session_connection <- function(connection) { + if (is.null(connection)) { + return(invisible(FALSE)) + } + + try(close(connection), silent = TRUE) + invisible(TRUE) +} + +pmetrics_prime_live_session_connection <- function(config) { + if (is.null(config)) { + return(NULL) + } + + if (pmetrics_same_live_session_config(pmetrics_live_session_runtime$config, config) && !is.null(pmetrics_live_session_runtime$connection)) { + return(pmetrics_live_session_runtime$connection) + } + + pmetrics_close_live_session_connection(pmetrics_live_session_runtime$connection) + pmetrics_live_session_runtime$config <- config + pmetrics_live_session_runtime$state <- pmetrics_new_live_session_state(config) + pmetrics_live_session_runtime$connection <- tryCatch( + pmetrics_open_live_session_connection(config), + error = function(e) NULL + ) + + if (is.null(pmetrics_live_session_runtime$connection)) { + pmetrics_live_session_runtime$state <- modifyList( + pmetrics_live_session_runtime$state, + list(connection = "failed", status = "Unable to connect to live session") + ) + } + + pmetrics_live_session_runtime$connection +} + +pmetrics_parse_live_session_message <- function(line) { + tryCatch( + jsonlite::fromJSON(line, simplifyVector = FALSE), + error = function(e) NULL + ) +} + +pmetrics_read_live_session_messages <- function(connection, max_messages = 50L) { + if (is.null(connection)) { + return(list()) + } + + ready <- tryCatch(socketSelect(list(connection), timeout = 0), error = function(e) list(FALSE)) + if (!length(ready) || !isTRUE(ready[[1]])) { + return(list()) + } + + lines <- tryCatch( + readLines(connection, n = as.integer(max_messages), warn = FALSE), + error = function(e) character() + ) + + Filter(Negate(is.null), lapply(lines, pmetrics_parse_live_session_message)) +} + +pmetrics_empty_live_objective_history <- function() { + data.frame( + cycle = integer(), + neg2ll = numeric(), + objective_delta = numeric(), + cycle_elapsed_ms = numeric(), + total_elapsed_ms = numeric(), + nspp = integer(), + status = character(), + stringsAsFactors = FALSE + ) +} + +pmetrics_empty_live_error_model_history <- function() { + data.frame( + cycle = integer(), + outeq = integer(), + kind = character(), + value = numeric(), + stringsAsFactors = FALSE + ) +} + +pmetrics_empty_live_parameter_history <- function() { + data.frame( + cycle = integer(), + parameter = character(), + mean = numeric(), + median = numeric(), + sd = numeric(), + stringsAsFactors = FALSE + ) +} + +pmetrics_new_live_session_state <- function(config) { + list( + view = "live_monitor", + run_state = "connecting", + connection = "connecting", + session_id = config$session_id, + status = "Waiting for fit", + cycle = NA_integer_, + neg2ll = NA_real_, + cycle_elapsed_ms = NA_real_, + total_elapsed_ms = NA_real_, + nspp = NA_integer_, + last_event = "waiting_for_fit", + last_update = as.POSIXct(NA), + pending_command = NULL, + command_status = "Pause takes effect before the next cycle. Stop waits for the current cycle to finish.", + objective_history = pmetrics_empty_live_objective_history(), + error_model_history = pmetrics_empty_live_error_model_history(), + parameter_history = pmetrics_empty_live_parameter_history(), + events = list() + ) +} + +pmetrics_append_live_session_event <- function(state, label, level = "info") { + events <- c(state$events, list(list(label = label, level = level, at = Sys.time()))) + if (length(events) > 20) { + events <- tail(events, 20) + } + state$events <- events + state$last_update <- Sys.time() + state +} + +pmetrics_normalize_live_status_value <- function(status) { + if (is.null(status) || !length(status)) { + return("") + } + + if (is.character(status)) { + return(as.character(status[[1]])) + } + + if (is.list(status)) { + if (length(status) == 1L && length(names(status)) == 1L && nzchar(names(status)[[1]])) { + return(trimws(paste(names(status)[[1]], pmetrics_normalize_live_status_value(status[[1]])))) + } + + values <- vapply(status, pmetrics_normalize_live_status_value, character(1), USE.NAMES = FALSE) + values <- values[nzchar(values)] + return(paste(values, collapse = " ")) + } + + as.character(status[[1]]) +} + +pmetrics_upsert_live_objective_history <- function(history, event) { + cycle <- as.integer(pmetrics_live_default(event$cycle, NA_integer_)) + history <- history[history$cycle != cycle, , drop = FALSE] + + row <- data.frame( + cycle = cycle, + neg2ll = as.numeric(pmetrics_live_default(event$neg2ll, NA_real_)), + objective_delta = as.numeric(pmetrics_live_default(event$objective_delta, NA_real_)), + cycle_elapsed_ms = as.numeric(pmetrics_live_default(event$cycle_elapsed_ms, NA_real_)), + total_elapsed_ms = as.numeric(pmetrics_live_default(event$total_elapsed_ms, NA_real_)), + nspp = as.integer(pmetrics_live_default(event$nspp, NA_integer_)), + status = pmetrics_normalize_live_status_value(pmetrics_live_default(event$status, "running")), + stringsAsFactors = FALSE + ) + + history <- rbind(history, row) + history[order(history$cycle), , drop = FALSE] +} + +pmetrics_upsert_live_error_model_history <- function(history, event) { + cycle <- as.integer(pmetrics_live_default(event$cycle, NA_integer_)) + history <- history[history$cycle != cycle, , drop = FALSE] + + error_models <- pmetrics_live_default(event$error_models, list()) + if (!length(error_models)) { + return(history) + } + + rows <- do.call( + rbind, + lapply(error_models, function(model) { + data.frame( + cycle = cycle, + outeq = as.integer(pmetrics_live_default(model$outeq, NA_integer_)), + kind = tolower(as.character(pmetrics_live_default(model$kind, "gamma"))), + value = as.numeric(pmetrics_live_default(model$value, NA_real_)), + stringsAsFactors = FALSE + ) + }) + ) + + history <- rbind(history, rows) + history[order(history$cycle, history$outeq), , drop = FALSE] +} + +pmetrics_upsert_live_parameter_history <- function(history, event) { + cycle <- as.integer(pmetrics_live_default(event$cycle, NA_integer_)) + history <- history[history$cycle != cycle, , drop = FALSE] + + parameters <- pmetrics_live_default(event$parameters, list()) + if (!length(parameters)) { + return(history) + } + + rows <- do.call( + rbind, + lapply(parameters, function(parameter) { + data.frame( + cycle = cycle, + parameter = as.character(pmetrics_live_default(parameter$name, "parameter")), + mean = as.numeric(pmetrics_live_default(parameter$mean, NA_real_)), + median = as.numeric(pmetrics_live_default(parameter$median, NA_real_)), + sd = as.numeric(pmetrics_live_default(parameter$sd, NA_real_)), + stringsAsFactors = FALSE + ) + }) + ) + + history <- rbind(history, rows) + history[order(history$cycle, history$parameter), , drop = FALSE] +} + +pmetrics_live_stage_label <- function(state) { + if (identical(state$view, "live_monitor")) { + "Live status" + } else { + "Connecting" + } +} + +pmetrics_live_run_state_label <- function(state) { + switch(pmetrics_live_default(state$run_state, "connecting"), + connecting = "Connecting", + running = "Running", + paused = "Paused", + stopping = "Stop requested", + stopped = "Stopped", + completed = "Completed", + failed = "Failed", + closed = "Closed", + tools::toTitleCase(gsub("_", " ", pmetrics_live_default(state$run_state, "connecting"), fixed = TRUE)) + ) +} + +pmetrics_live_error_model_label <- function(state) { + history <- state$error_model_history + if (!nrow(history)) { + return("Gamma/Lambda") + } + + kinds <- unique(stats::na.omit(history$kind)) + if (length(kinds) != 1L) { + return("Gamma/Lambda") + } + + if (identical(kinds[[1]], "lambda")) "Lambda" else "Gamma" +} + +pmetrics_live_error_model_type <- function(state) { + if (identical(pmetrics_live_error_model_label(state), "Lambda")) { + "Additive" + } else { + "Proportional" + } +} + +pmetrics_format_live_elapsed <- function(total_elapsed_ms) { + if (is.null(total_elapsed_ms) || !length(total_elapsed_ms) || is.na(total_elapsed_ms)) { + return("Waiting") + } + + seconds <- as.integer(round(as.numeric(total_elapsed_ms) / 1000)) + hours <- seconds %/% 3600 + minutes <- (seconds %% 3600) %/% 60 + secs <- seconds %% 60 + + if (hours > 0) { + sprintf("%02d:%02d:%02d", hours, minutes, secs) + } else { + sprintf("%02d:%02d", minutes, secs) + } +} + +pmetrics_live_parameter_metric_table <- function(state, metric) { + history <- state$parameter_history + if (!nrow(history)) { + return(data.frame()) + } + + metric <- match.arg(metric, choices = c("mean", "median", "sd")) + cycles <- sort(unique(history$cycle)) + parameters <- sort(unique(history$parameter)) + + rows <- lapply(cycles, function(cycle) { + cycle_rows <- history[history$cycle == cycle, , drop = FALSE] + values <- stats::setNames(as.list(rep(NA_real_, length(parameters))), parameters) + + for (index in seq_len(nrow(cycle_rows))) { + values[[cycle_rows$parameter[[index]]]] <- cycle_rows[[metric]][[index]] + } + + data.frame( + cycle = cycle, + as.data.frame(values, stringsAsFactors = FALSE, check.names = FALSE), + check.names = FALSE, + stringsAsFactors = FALSE + ) + }) + + do.call(rbind, rows) +} + +pmetrics_live_cycle_result <- function(state) { + error_model_history <- state$error_model_history + gamlam <- if (nrow(error_model_history)) { + transform( + error_model_history[, c("cycle", "outeq", "value"), drop = FALSE], + type = pmetrics_live_error_model_type(state) + ) + } else { + data.frame(cycle = integer(), outeq = integer(), value = numeric(), type = character(), stringsAsFactors = FALSE) + } + + list( + cycle = list( + objective = state$objective_history[, c("cycle", "neg2ll"), drop = FALSE], + gamlam = gamlam, + mean = pmetrics_live_parameter_metric_table(state, "mean"), + median = pmetrics_live_parameter_metric_table(state, "median"), + sd = pmetrics_live_parameter_metric_table(state, "sd"), + data = list(gamlam = list(type = pmetrics_live_error_model_type(state))) + ) + ) +} + +pmetrics_latest_live_error_model_table <- function(state) { + history <- state$error_model_history + if (!nrow(history)) { + return(data.frame()) + } + + latest_cycle <- max(history$cycle, na.rm = TRUE) + latest <- history[history$cycle == latest_cycle, , drop = FALSE] + + data.frame( + Outeq = latest$outeq, + Kind = tools::toTitleCase(latest$kind), + Value = latest$value, + stringsAsFactors = FALSE, + check.names = FALSE + ) +} + +pmetrics_latest_live_parameter_table <- function(state) { + history <- state$parameter_history + if (!nrow(history)) { + return(data.frame()) + } + + latest_cycle <- max(history$cycle, na.rm = TRUE) + latest <- history[history$cycle == latest_cycle, , drop = FALSE] + + data.frame( + Parameter = latest$parameter, + Mean = latest$mean, + Median = latest$median, + SD = latest$sd, + stringsAsFactors = FALSE, + check.names = FALSE + ) +} + +pmetrics_live_html_table <- function(data, digits = 3) { + format_cell <- function(x) { + if (is.numeric(x)) { + formatC(x, digits = digits, format = "fg", flag = "#") + } else if (is.logical(x)) { + ifelse(x, "TRUE", "FALSE") + } else { + as.character(x) + } + } + + rows <- apply(data, 1, function(row) { + htmltools::tags$tr( + lapply(row, function(cell) htmltools::tags$td(format_cell(cell))) + ) + }) + + htmltools::tags$table( + class = "table table-sm table-striped table-hover", + htmltools::tags$thead(htmltools::tags$tr(lapply(names(data), htmltools::tags$th))), + htmltools::tags$tbody(rows) + ) +} + +pmetrics_build_live_empty_plot <- function(title) { + ggplot2::ggplot() + + ggplot2::theme_void() + + ggplot2::labs(title = title) +} + +pmetrics_build_cycle_objective_plot <- function(res, metric = "neg2ll", gamlam_label = "Gamma") { + if (is.null(res$cycle$objective)) { + return(ggplot2::ggplot() + ggplot2::theme_void() + ggplot2::labs(title = "No cycle objective data available.")) + } + + data <- as.data.frame(res$cycle$objective) + if (!"cycle" %in% names(data)) { + return(ggplot2::ggplot() + ggplot2::theme_void() + ggplot2::labs(title = "Cycle objective data incomplete.")) + } + + metric <- tolower(metric) + + if (metric == "gamlam") { + if (is.null(res$cycle$gamlam)) { + return(ggplot2::ggplot() + ggplot2::theme_void() + ggplot2::labs(title = "No gamma/lambda cycle data available.")) + } + + gamlam_df <- as.data.frame(res$cycle$gamlam) + if (!all(c("cycle", "value") %in% names(gamlam_df))) { + return(ggplot2::ggplot() + ggplot2::theme_void() + ggplot2::labs(title = "Gamma/lambda cycle data incomplete.")) + } + + ggplot2::ggplot(gamlam_df, ggplot2::aes(x = cycle, y = value)) + + ggplot2::geom_line(colour = "#2c3e50") + + ggplot2::geom_point(colour = "#2c3e50", fill = "#e74c3c", shape = 21, size = 2) + + ggplot2::theme_classic() + + ggplot2::labs(x = "Cycle", y = gamlam_label) + } else if (metric %in% c("norm_mean", "norm_median", "norm_sd")) { + norm_source <- sub("^norm_", "", metric) + norm_tbl <- res$cycle[[norm_source]] + if (is.null(norm_tbl)) { + return(ggplot2::ggplot() + ggplot2::theme_void() + ggplot2::labs(title = "No normalized cycle data available.")) + } + + norm_df <- as.data.frame(norm_tbl) + if (!"cycle" %in% names(norm_df)) { + return(ggplot2::ggplot() + ggplot2::theme_void() + ggplot2::labs(title = "Normalized cycle data incomplete.")) + } + + param_cols <- setdiff(names(norm_df), "cycle") + if (!length(param_cols)) { + return(ggplot2::ggplot() + ggplot2::theme_void() + ggplot2::labs(title = "No parameter columns available for normalization plot.")) + } + + long_df <- do.call( + rbind, + lapply(param_cols, function(par) { + vals <- norm_df[[par]] + base_val <- vals[[1]] + if (is.na(base_val) || identical(base_val, 0)) { + base_val <- 1 + } + + data.frame( + cycle = norm_df$cycle, + parameter = par, + value = vals / base_val, + stringsAsFactors = FALSE + ) + }) + ) + + ggplot2::ggplot(long_df, ggplot2::aes(x = cycle, y = value, colour = parameter)) + + ggplot2::geom_line(linewidth = 0.8) + + ggplot2::geom_point(size = 1.5) + + ggplot2::theme_classic() + + ggplot2::labs(x = "Cycle", y = "Normalized value", colour = "Parameter", subtitle = paste0("Normalized by ", tools::toTitleCase(norm_source))) + } else { + if (!metric %in% tolower(names(data))) { + metric <- "neg2ll" + } + + metric_name <- names(data)[tolower(names(data)) == metric][1] + if (is.na(metric_name) || !nzchar(metric_name)) { + metric_name <- "neg2ll" + } + + data$criterion_value <- data[[metric_name]] + y_label <- switch(tolower(metric_name), neg2ll = "-2 Log Likelihood", aic = "AIC", bic = "BIC", metric_name) + + ggplot2::ggplot(data, ggplot2::aes(x = cycle, y = criterion_value)) + + ggplot2::geom_line(colour = "#2c3e50") + + ggplot2::geom_point(colour = "#2c3e50", fill = "#e74c3c", shape = 21, size = 2) + + ggplot2::theme_classic() + + ggplot2::labs(x = "Cycle", y = y_label) + } +} + +pmetrics_build_live_objective_plot <- function(state) { + if (!nrow(state$objective_history)) { + return(pmetrics_build_live_empty_plot("Waiting for the first completed cycle.")) + } + + pmetrics_build_cycle_objective_plot( + res = pmetrics_live_cycle_result(state), + metric = "neg2ll", + gamlam_label = pmetrics_live_error_model_label(state) + ) +} + +pmetrics_build_live_error_model_plot <- function(state) { + history <- state$error_model_history + if (!nrow(history)) { + return(pmetrics_build_live_empty_plot("Waiting for gamma/lambda cycle data.")) + } + + plot_data <- history + if (length(unique(plot_data$outeq)) > 1L) { + plot_data$series <- paste("Outeq", plot_data$outeq) + } else { + plot_data$series <- pmetrics_live_error_model_label(state) + } + + ggplot2::ggplot(plot_data, ggplot2::aes(x = cycle, y = value, colour = series, group = series)) + + ggplot2::geom_line(linewidth = 0.8) + + ggplot2::geom_point(size = 1.8) + + ggplot2::theme_classic() + + ggplot2::labs(x = "Cycle", y = pmetrics_live_error_model_label(state), colour = if (length(unique(plot_data$series)) > 1L) "Output" else NULL) +} + +pmetrics_build_live_parameter_plot <- function(state, metric = "mean") { + metric <- match.arg(metric, choices = c("mean", "median", "sd")) + + if (!nrow(state$parameter_history)) { + return(pmetrics_build_live_empty_plot("Waiting for parameter trend data.")) + } + + pmetrics_build_cycle_objective_plot( + res = pmetrics_live_cycle_result(state), + metric = paste0("norm_", metric), + gamlam_label = pmetrics_live_error_model_label(state) + ) +} + +pmetrics_live_control_state <- function(state) { + run_state <- pmetrics_live_default(state$run_state, "connecting") + pending <- pmetrics_live_default(state$pending_command, "") + terminal <- run_state %in% c("completed", "stopped", "failed", "closed") || identical(state$connection, "failed") + active <- identical(state$view, "live_monitor") && !terminal + + list( + pause_enabled = active && identical(run_state, "running") && !identical(pending, "pause_after_cycle"), + resume_enabled = active && identical(run_state, "paused") && !identical(pending, "resume"), + stop_enabled = active && run_state %in% c("running", "paused") && !identical(pending, "stop_after_cycle") + ) +} + +pmetrics_live_command_help <- function(kind) { + switch(kind, + pause_after_cycle = "Pause requested. It will take effect before the next cycle starts.", + resume = "Resume requested. The run will continue at the next cycle boundary.", + stop_after_cycle = "Stop requested. The current cycle will finish before the run stops.", + ping = "Ping sent.", + "Command sent." + ) +} + +pmetrics_send_live_session_command <- function(connection, kind) { + if (is.null(connection)) { + return(list(ok = FALSE, message = "Live session connection is not available.")) + } + + payload <- jsonlite::toJSON(list(kind = kind), auto_unbox = TRUE) + + tryCatch( + { + writeLines(payload, connection, sep = "\n", useBytes = TRUE) + try(flush(connection), silent = TRUE) + list(ok = TRUE, message = pmetrics_live_command_help(kind)) + }, + error = function(e) { + list(ok = FALSE, message = conditionMessage(e)) + } + ) +} + +pmetrics_describe_live_fit_completed <- function(event) { + cycles <- pmetrics_live_default(event$cycles, "?") + status_label <- pmetrics_normalize_live_status_value(event$status) + + if (grepl("stopped", status_label, ignore.case = TRUE)) { + return(list(run_state = "stopped", label = sprintf("Fit stopped after %s cycles", cycles))) + } + + if (grepl("converged", status_label, ignore.case = TRUE)) { + return(list(run_state = "completed", label = sprintf("Fit converged after %s cycles", cycles))) + } + + if (grepl("max", status_label, ignore.case = TRUE)) { + return(list(run_state = "completed", label = sprintf("Fit reached the cycle limit after %s cycles", cycles))) + } + + list(run_state = "completed", label = sprintf("Fit completed after %s cycles", cycles)) +} + +pmetrics_format_live_message_label <- function(message) { + kind <- pmetrics_live_default(message$kind, "unknown") + + if (identical(kind, "session_started")) { + return("Session started") + } + + if (identical(kind, "fit_failed")) { + return(paste("Fit failed:", pmetrics_live_default(message$message, "Fit failed"))) + } + + if (identical(kind, "session_closed")) { + return("Session closed") + } + + if (identical(kind, "progress") && is.list(message$event)) { + event_kind <- pmetrics_live_default(message$event$kind, "progress") + if (identical(event_kind, "nonparametric_cycle")) { + return(sprintf("Cycle %s completed", pmetrics_live_default(message$event$cycle, "?"))) + } + if (identical(event_kind, "paused")) { + return(sprintf("Paused after cycle %s", pmetrics_live_default(message$event$cycle, "?"))) + } + if (identical(event_kind, "resumed")) { + return(sprintf("Resumed at cycle %s", pmetrics_live_default(message$event$cycle, "?"))) + } + if (identical(event_kind, "stop_requested")) { + return(sprintf("Stop requested after cycle %s", pmetrics_live_default(message$event$cycle, "?"))) + } + if (identical(event_kind, "fit_completed")) { + return(sprintf("Fit completed after %s cycles", pmetrics_live_default(message$event$cycles, "?"))) + } + return(gsub("_", " ", event_kind, fixed = TRUE)) + } + + gsub("_", " ", kind, fixed = TRUE) +} + +pmetrics_reduce_live_session_state <- function(state, messages) { + for (message in messages) { + state$connection <- "connected" + state <- pmetrics_append_live_session_event(state, pmetrics_format_live_message_label(message)) + + kind <- pmetrics_live_default(message$kind, "unknown") + state$last_event <- kind + + if (identical(kind, "session_started")) { + state$status <- "Session started" + next + } + + if (identical(kind, "fit_failed")) { + state$run_state <- "failed" + state$connection <- "failed" + state$status <- pmetrics_live_default(message$message, "Fit failed") + state$pending_command <- NULL + state$command_status <- "Run failed." + next + } + + if (identical(kind, "session_closed")) { + state$connection <- "closed" + if (!state$run_state %in% c("completed", "stopped", "failed")) { + state$run_state <- "closed" + state$status <- "Session closed" + } + state$pending_command <- NULL + next + } + + if (!identical(kind, "progress") || !is.list(message$event)) { + next + } + + event <- message$event + event_kind <- pmetrics_live_default(event$kind, "progress") + state$last_event <- event_kind + + if (identical(event_kind, "fit_started")) { + state$run_state <- "running" + state$status <- "Fit started" + next + } + + if (identical(event_kind, "paused")) { + state$run_state <- "paused" + state$status <- sprintf("Paused after cycle %s", pmetrics_live_default(event$cycle, "?")) + state$pending_command <- NULL + state$command_status <- sprintf("Pause acknowledged after cycle %s.", pmetrics_live_default(event$cycle, "?")) + next + } + + if (identical(event_kind, "resumed")) { + state$run_state <- "running" + state$status <- sprintf("Resumed at cycle %s", pmetrics_live_default(event$cycle, "?")) + state$pending_command <- NULL + state$command_status <- sprintf("Resume acknowledged at cycle %s.", pmetrics_live_default(event$cycle, "?")) + next + } + + if (identical(event_kind, "stop_requested")) { + state$run_state <- "stopping" + state$status <- sprintf("Stop requested after cycle %s", pmetrics_live_default(event$cycle, "?")) + state$pending_command <- NULL + state$command_status <- sprintf("Stop acknowledged after cycle %s.", pmetrics_live_default(event$cycle, "?")) + next + } + + if (identical(event_kind, "fit_completed")) { + completion <- pmetrics_describe_live_fit_completed(event) + state$run_state <- completion$run_state + state$status <- completion$label + state$pending_command <- NULL + state$command_status <- completion$label + next + } + + if (identical(event_kind, "nonparametric_cycle")) { + if (!identical(state$run_state, "stopping")) { + state$run_state <- "running" + } + state$cycle <- as.integer(pmetrics_live_default(event$cycle, NA_integer_)) + state$neg2ll <- as.numeric(pmetrics_live_default(event$neg2ll, NA_real_)) + state$cycle_elapsed_ms <- as.numeric(pmetrics_live_default(event$cycle_elapsed_ms, NA_real_)) + state$total_elapsed_ms <- as.numeric(pmetrics_live_default(event$total_elapsed_ms, NA_real_)) + state$nspp <- as.integer(pmetrics_live_default(event$nspp, NA_integer_)) + state$status <- sprintf("Cycle %s completed", pmetrics_live_default(event$cycle, "?")) + state$objective_history <- pmetrics_upsert_live_objective_history(state$objective_history, event) + state$error_model_history <- pmetrics_upsert_live_error_model_history(state$error_model_history, event) + state$parameter_history <- pmetrics_upsert_live_parameter_history(state$parameter_history, event) + } + } + + state +} + +pmetrics_live_summary_card <- function(title, output_id, detail_output_id = NULL, classes = NULL) { + htmltools::tags$div( + class = paste(c("live-summary-card", classes), collapse = " "), + htmltools::tags$div(class = "live-summary-card__label", title), + htmltools::tags$div(class = "live-summary-card__value", shiny::textOutput(output_id)), + if (!is.null(detail_output_id)) { + htmltools::tags$div(class = "live-summary-card__detail", shiny::textOutput(detail_output_id)) + } + ) +} + +pmetrics_live_add_external_resources <- function() { + shiny::tags$head( + shiny::tags$title("Pmetrics Live Status"), + shiny::tags$link(rel = "stylesheet", type = "text/css", href = "custom.css"), + shiny::tags$script(shiny::HTML( + "Shiny.addCustomMessageHandler('pmetrics-live-close', function(message) { window.close(); });" + )) + ) +} + +pmetrics_live_app_ui <- function(request) { + shiny::tagList( + pmetrics_live_add_external_resources(), + bslib::page_fluid( + title = "Pmetrics Live Status", + theme = bslib::bs_theme( + bootswatch = "flatly", + primary = "#2c3e50", + "card-border-radius" = "0.5rem" + ), + shiny::div( + class = "live-report-page", + shiny::div( + class = "app-banner", + shiny::tags$span(class = "app-banner__title", "Pmetrics Live Status"), + shiny::tags$div( + class = "app-banner__actions", + shiny::uiOutput("live_mode_badge"), + shiny::actionButton( + "close_app", + "Close", + class = "btn-danger app-banner__close", + onclick = "setTimeout(function(){window.close();}, 100);" + ) + ) + ), + shiny::uiOutput("live_banner"), + htmltools::tags$div( + class = "live-summary-grid", + pmetrics_live_summary_card("Mode", "live_mode"), + pmetrics_live_summary_card("Connection", "live_connection_state", detail_output_id = "live_connection_detail", classes = "live-summary-card--compact"), + pmetrics_live_summary_card("Run state", "live_run_state"), + pmetrics_live_summary_card("Cycle", "live_cycle"), + pmetrics_live_summary_card("Objective", "live_objective"), + pmetrics_live_summary_card("Elapsed", "live_elapsed"), + pmetrics_live_summary_card("Support points", "live_nspp") + ), + htmltools::tags$div( + class = "live-command-strip", + htmltools::tags$div(class = "live-command-strip__label", "Latest status"), + htmltools::tags$div(class = "live-command-strip__value", shiny::textOutput("live_command_status")) + ), + bslib::layout_columns( + col_widths = c(7, 5), + bslib::card( + class = "live-panel-card live-panel-card--plot", + bslib::card_header("Objective by cycle"), + bslib::card_body(plotly::plotlyOutput("live_objective_plot", height = "420px")) + ), + bslib::card( + class = "live-panel-card live-panel-card--plot", + bslib::card_header(shiny::textOutput("live_error_model_header")), + bslib::card_body(plotly::plotlyOutput("live_error_model_plot", height = "420px")) + ) + ), + bslib::layout_columns( + col_widths = c(8, 4), + bslib::card( + class = "live-panel-card live-panel-card--plot", + bslib::card_header("Parameter trends by cycle"), + bslib::card_body( + shiny::uiOutput("live_parameter_controls"), + plotly::plotlyOutput("live_parameter_plot", height = "460px") + ) + ), + bslib::card( + class = "live-panel-card", + bslib::card_header("Run controls"), + bslib::card_body(shiny::uiOutput("live_controls")) + ) + ), + bslib::layout_columns( + col_widths = c(5, 7), + bslib::card( + class = "live-panel-card", + bslib::card_header("Current cycle detail"), + bslib::card_body(shiny::uiOutput("live_current_cycle_detail")) + ), + bslib::card( + class = "live-panel-card live-panel-card--events", + bslib::card_header("Recent events"), + bslib::card_body(shiny::uiOutput("live_events")) + ) + ) + ) + ) + ) +} + +pmetrics_live_app_server <- function(input, output, session) { + config <- pmetrics_live_session_config() + live_connection <- pmetrics_prime_live_session_connection(config) + live_state <- shiny::reactiveVal(pmetrics_get_live_session_state(config)) + + if (is.null(live_connection)) { + state <- modifyList( + pmetrics_get_live_session_state(config), + list(connection = "failed", status = "Unable to connect to live session") + ) + pmetrics_set_live_session_state(config, state) + live_state(state) + } + + queue_live_command <- function(kind) { + state <- pmetrics_get_live_session_state(config) + result <- pmetrics_send_live_session_command(live_connection, kind) + + if (isTRUE(result$ok)) { + state$pending_command <- kind + state$command_status <- result$message + state <- pmetrics_append_live_session_event( + state, + switch(kind, + pause_after_cycle = "Pause requested", + resume = "Resume requested", + stop_after_cycle = "Stop requested", + ping = "Ping sent", + "Command sent" + ) + ) + } else { + state$command_status <- paste("Command failed:", result$message) + state <- pmetrics_append_live_session_event(state, state$command_status, level = "danger") + } + + pmetrics_set_live_session_state(config, state) + live_state(state) + } + + shiny::observeEvent(input$close_app, { + shiny::stopApp() + }, ignoreInit = TRUE) + + shiny::observeEvent(input$pause_after_cycle, { + queue_live_command("pause_after_cycle") + }, ignoreInit = TRUE) + + shiny::observeEvent(input$resume_fit, { + queue_live_command("resume") + }, ignoreInit = TRUE) + + shiny::observeEvent(input$stop_after_cycle, { + queue_live_command("stop_after_cycle") + }, ignoreInit = TRUE) + + shiny::observe({ + shiny::invalidateLater(200, session) + + messages <- pmetrics_read_live_session_messages(live_connection) + if (!length(messages)) { + live_state(pmetrics_get_live_session_state(config)) + return(invisible(NULL)) + } + + state <- pmetrics_reduce_live_session_state(pmetrics_get_live_session_state(config), messages) + pmetrics_set_live_session_state(config, state) + live_state(state) + + if (identical(state$connection, "closed") && state$run_state %in% c("completed", "stopped")) { + session$sendCustomMessage("pmetrics-live-close", list()) + } + }) + + output$live_mode_badge <- shiny::renderUI({ + state <- live_state() + htmltools::tags$span(class = "btn btn-primary", pmetrics_live_stage_label(state)) + }) + + output$live_banner <- shiny::renderUI({ + state <- live_state() + banner_class <- if (identical(state$run_state, "failed")) { + "alert alert-danger" + } else { + "alert alert-primary" + } + + htmltools::tags$div( + class = banner_class, + htmltools::tags$strong(paste0(pmetrics_live_stage_label(state), ": ")), + state$status + ) + }) + + output$live_mode <- shiny::renderText({ + pmetrics_live_stage_label(live_state()) + }) + + output$live_connection_state <- shiny::renderText({ + tools::toTitleCase(live_state()$connection) + }) + + output$live_connection_detail <- shiny::renderText({ + paste("Session", live_state()$session_id) + }) + + output$live_run_state <- shiny::renderText({ + pmetrics_live_run_state_label(live_state()) + }) + + output$live_cycle <- shiny::renderText({ + cycle <- live_state()$cycle + if (is.na(cycle)) "Waiting" else as.character(cycle) + }) + + output$live_objective <- shiny::renderText({ + objective <- live_state()$neg2ll + if (is.na(objective)) "Waiting" else format(round(objective, 4), nsmall = 4) + }) + + output$live_elapsed <- shiny::renderText({ + pmetrics_format_live_elapsed(live_state()$total_elapsed_ms) + }) + + output$live_nspp <- shiny::renderText({ + nspp <- live_state()$nspp + if (is.na(nspp)) "Waiting" else as.character(nspp) + }) + + output$live_command_status <- shiny::renderText({ + live_state()$command_status + }) + + output$live_objective_plot <- plotly::renderPlotly({ + plotly::ggplotly(pmetrics_build_live_objective_plot(live_state()), tooltip = c("x", "y")) + }) + + output$live_error_model_header <- shiny::renderText({ + paste0(pmetrics_live_error_model_label(live_state()), " by cycle") + }) + + output$live_error_model_plot <- plotly::renderPlotly({ + plotly::ggplotly(pmetrics_build_live_error_model_plot(live_state()), tooltip = c("x", "y", "colour")) + }) + + output$live_parameter_controls <- shiny::renderUI({ + htmltools::tags$div( + class = "op-controls-row", + shiny::radioButtons( + inputId = "live_parameter_metric", + label = NULL, + choices = c("Mean" = "mean", "Median" = "median", "SD" = "sd"), + selected = if (is.null(input$live_parameter_metric) || !input$live_parameter_metric %in% c("mean", "median", "sd")) "mean" else input$live_parameter_metric, + inline = TRUE + ) + ) + }) + + output$live_parameter_plot <- plotly::renderPlotly({ + metric <- if (is.null(input$live_parameter_metric)) "mean" else input$live_parameter_metric + plotly::ggplotly(pmetrics_build_live_parameter_plot(live_state(), metric = metric), tooltip = c("x", "y", "colour")) + }) + + output$live_controls <- shiny::renderUI({ + state <- live_state() + controls <- pmetrics_live_control_state(state) + + htmltools::tagList( + htmltools::tags$p(state$command_status), + htmltools::tags$p( + class = "text-muted", + "Pause waits for the current cycle to finish. Stop also waits for the current cycle boundary." + ), + htmltools::tags$div( + class = "d-flex gap-2 flex-wrap", + shiny::actionButton( + "pause_after_cycle", + "Pause", + class = "btn btn-outline-primary", + disabled = if (!isTRUE(controls$pause_enabled)) "disabled" else NULL + ), + shiny::actionButton( + "resume_fit", + "Resume", + class = "btn btn-outline-success", + disabled = if (!isTRUE(controls$resume_enabled)) "disabled" else NULL + ), + shiny::actionButton( + "stop_after_cycle", + "Stop After Cycle", + class = "btn btn-outline-danger", + disabled = if (!isTRUE(controls$stop_enabled)) "disabled" else NULL + ) + ) + ) + }) + + output$live_current_cycle_detail <- shiny::renderUI({ + state <- live_state() + error_models <- pmetrics_latest_live_error_model_table(state) + parameters <- pmetrics_latest_live_parameter_table(state) + + if (!nrow(error_models) && !nrow(parameters)) { + return(htmltools::tags$p("Waiting for the first completed cycle.")) + } + + htmltools::tagList( + htmltools::tags$p(htmltools::tags$strong("Status: "), state$status), + htmltools::tags$p( + htmltools::tags$strong("Last update: "), + if (is.na(state$last_update)) "Waiting" else format(state$last_update, "%H:%M:%S") + ), + if (nrow(error_models)) { + htmltools::tagList( + htmltools::tags$h5(pmetrics_live_error_model_label(state)), + pmetrics_live_html_table(error_models, digits = 4) + ) + }, + if (nrow(parameters)) { + htmltools::tagList( + htmltools::tags$h5("Parameters"), + pmetrics_live_html_table(parameters, digits = 4) + ) + } + ) + }) + + output$live_events <- shiny::renderUI({ + events <- live_state()$events + if (!length(events)) { + return(shiny::tags$p("Waiting for live session events.")) + } + + shiny::tags$ul( + class = "list-unstyled", + lapply(rev(events), function(event) { + shiny::tags$li(shiny::tags$strong(format(event$at, "%H:%M:%S")), " ", event$label) + }) + ) + }) +} \ No newline at end of file diff --git a/R/PM_model.R b/R/PM_model.R index 21dda402b..b092e0d9e 100644 --- a/R/PM_model.R +++ b/R/PM_model.R @@ -1365,7 +1365,7 @@ PM_model <- R6::R6Class( live_report_session <- tryCatch( start_live_report_session(show = TRUE), error = function(e) { - msg <<- c(msg, "Live reporting app could not be launched before the fit started.") + msg <<- c(msg, "Live status app could not be launched before the fit started.") NULL } ) @@ -1373,7 +1373,7 @@ PM_model <- R6::R6Class( if (!is.null(live_report_session)) { live_report_started <- TRUE if (!isTRUE(live_report_session$connected)) { - msg <- c(msg, "Live reporting app did not connect before the first fit cycle.") + msg <- c(msg, "Live status app did not connect before the first fit cycle.") } on.exit(close_live_report_session(live_report_session), add = TRUE) } @@ -1453,25 +1453,24 @@ PM_model <- R6::R6Class( ) if (live_report_started) { - tryCatch( - send_live_report_result(live_report_session, res), + msg <- c(msg, "Live status app launched.") + if (!is.null(live_report_session$url) && nzchar(live_report_session$url)) { + msg <- c(msg, "Live status app URL: {live_report_session$url}") + } + + valid_report <- tryCatch( + PM_report(res, path = normalizePath(out_path), template = report, quiet = TRUE), error = function(e) { - msg <<- c(msg, "Live reporting app could not switch to the finished report.") - try( - send_live_report_failure( - live_report_session, - paste("Finished report handoff failed:", conditionMessage(e)) - ), - silent = TRUE - ) - invisible(NULL) + -1 } ) - msg <- c(msg, "Live reporting app launched.") - if (!is.null(live_report_session$url) && nzchar(live_report_session$url)) { - msg <- c(msg, "Live reporting app URL: {live_report_session$url}") + if (valid_report == 1) { + msg <- c(msg, "Finished reporting app launched.") + } else { + msg <- c(msg, "Finished reporting app could not be launched.") } + msg <- c(msg, "If assigned to a variable, e.g. {.code run{run} <-}, results are available in {.code run{run}}.") } else if (report != "none") { valid_report <- tryCatch( @@ -2528,6 +2527,37 @@ plot.PM_model <- function(x, build_sum(terms) } + # Extract x[i] pattern from expression + extract_x_pattern <- function(expr) { + if (is.call(expr) && as.character(expr[[1]]) == "[" && + length(expr) == 3 && as.character(expr[[2]]) == "x") { + return(as.numeric(as.character(expr[[3]]))) + } + return(NULL) + } + + # Find x[i] pattern in any expression + find_x_in_expression <- function(expr) { + if (is.call(expr)) { + # Check current expression + x_idx <- extract_x_pattern(expr) + if (!is.null(x_idx)) { + return(x_idx) + } + + # Recursively check sub-expressions + for (i in 1:length(expr)) { + if (i > 1) { # Skip the function name + x_idx <- find_x_in_expression(expr[[i]]) + if (!is.null(x_idx)) { + return(x_idx) + } + } + } + } + return(NULL) + } + # Parse output equations parse_output_equations <- function(equations) { # if (is.null(func)) return(list()) @@ -2565,28 +2595,6 @@ plot.PM_model <- function(x, return(outputs) } - # Find x[i] pattern in any expression - find_x_in_expression <- function(expr) { - if (is.call(expr)) { - # Check current expression - x_idx <- extract_x_pattern(expr) - if (!is.null(x_idx)) { - return(x_idx) - } - - # Recursively check sub-expressions - for (i in 1:length(expr)) { - if (i > 1) { # Skip the function name - x_idx <- find_x_in_expression(expr[[i]]) - if (!is.null(x_idx)) { - return(x_idx) - } - } - } - } - return(NULL) - } - # Parse terms from right-hand side recursively parse_rhs_terms <- function(rhs_expr) { terms <- list() @@ -2636,15 +2644,6 @@ plot.PM_model <- function(x, return(terms) } - # Extract x[i] pattern from expression - extract_x_pattern <- function(expr) { - if (is.call(expr) && as.character(expr[[1]]) == "[" && - length(expr) == 3 && as.character(expr[[2]]) == "x") { - return(as.numeric(as.character(expr[[3]]))) - } - return(NULL) - } - # Extract compartment connections extract_connections <- function(equations) { compartments <- c() diff --git a/R/PM_report.R b/R/PM_report.R index b36c89bf4..ae476871f 100755 --- a/R/PM_report.R +++ b/R/PM_report.R @@ -26,17 +26,25 @@ PM_report <- function(x, template, path, show = TRUE, quiet = TRUE) { getNamespaceInfo(asNamespace("Pmetrics"), "path"), error = function(e) "" ) - sibling_reports_path <- normalizePath( + reports_candidates <- unique(c( + Sys.getenv("PMETRICS_REPORTS_PATH", unset = ""), + file.path(dirname(pmetrics_path), "PmetricsReports"), file.path(dirname(pmetrics_path), "Pmetricsreports"), - mustWork = FALSE - ) + file.path(pmetrics_path, "..", "Apps", "PmetricsReports"), + file.path(pmetrics_path, "..", "Apps", "Pmetricsreports") + )) + + reports_candidates <- reports_candidates[nzchar(reports_candidates)] + reports_candidates <- normalizePath(reports_candidates, mustWork = FALSE) + dev_reports_path <- reports_candidates[ + file.exists(file.path(reports_candidates, "DESCRIPTION")) + ][[1]] - if (nzchar(sibling_reports_path) && - file.exists(file.path(sibling_reports_path, "DESCRIPTION")) && + if (!is.null(dev_reports_path) && nzchar(dev_reports_path) && requireNamespace("pkgload", quietly = TRUE)) { loaded <- tryCatch( { - pkgload::load_all(sibling_reports_path, quiet = TRUE, export_all = FALSE) + pkgload::load_all(dev_reports_path, quiet = TRUE, export_all = FALSE) TRUE }, error = function(e) FALSE diff --git a/inst/apps/live-status/app.R b/inst/apps/live-status/app.R new file mode 100644 index 000000000..6889ac7b5 --- /dev/null +++ b/inst/apps/live-status/app.R @@ -0,0 +1,4 @@ +shiny::shinyApp( + ui = Pmetrics:::pmetrics_live_app_ui, + server = Pmetrics:::pmetrics_live_app_server +) \ No newline at end of file diff --git a/inst/apps/live-status/www/custom.css b/inst/apps/live-status/www/custom.css new file mode 100644 index 000000000..a5085ddfa --- /dev/null +++ b/inst/apps/live-status/www/custom.css @@ -0,0 +1,179 @@ +.live-report-page { + font-size: 0.96rem; + line-height: 1.4; + margin: 0 auto; + max-width: 1500px; + padding-bottom: 2rem; +} + +.app-banner { + align-items: center; + display: flex; + gap: 1rem; + justify-content: space-between; + width: 100%; +} + +.app-banner__title { + font-size: clamp(1.8rem, 2.4vw, 2.35rem); + font-weight: 700; + line-height: 1.05; +} + +.live-summary-grid { + display: grid; + gap: 1rem; + grid-template-columns: repeat(3, minmax(0, 1fr)); + margin-bottom: 1rem; +} + +.live-summary-card { + background: linear-gradient(180deg, #ffffff 0%, #f5f8fb 100%); + border: 1px solid #d9e1e8; + border-radius: 0.9rem; + box-shadow: 0 12px 30px rgba(44, 62, 80, 0.08); + min-height: 124px; + padding: 1rem 1.1rem; +} + +.live-summary-card__label { + color: #5a7186; + font-size: 0.78rem; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.live-summary-card__value > .shiny-text-output { + color: #182838; + font-size: 1.65rem; + font-weight: 700; + line-height: 1.15; + margin-top: 0.55rem; + overflow-wrap: anywhere; +} + +.live-summary-card__detail > .shiny-text-output { + color: #6d7f90; + font-size: 0.9rem; + margin-top: 0.45rem; + overflow-wrap: anywhere; +} + +.live-summary-card--compact .live-summary-card__value > .shiny-text-output { + font-size: 1.2rem; + line-height: 1.3; +} + +.live-command-strip { + align-items: flex-start; + background: #eef4f8; + border: 1px solid #d7e2ea; + border-radius: 0.9rem; + display: flex; + gap: 1rem; + margin-bottom: 1rem; + padding: 0.95rem 1.1rem; +} + +.live-command-strip__label { + color: #40576c; + font-size: 0.78rem; + font-weight: 700; + letter-spacing: 0.04em; + padding-top: 0.15rem; + text-transform: uppercase; + white-space: nowrap; +} + +.live-command-strip__value { + flex: 1; + min-width: 0; +} + +.live-command-strip__value > .shiny-text-output { + color: #1d3246; + font-size: 1rem; + font-weight: 600; + line-height: 1.4; + overflow-wrap: anywhere; +} + +.live-panel-card { + border-radius: 1rem; + box-shadow: 0 12px 30px rgba(44, 62, 80, 0.08); + height: 100%; +} + +.live-panel-card .card-header { + font-size: 1.12rem; + font-weight: 700; + padding: 1rem 1.2rem; +} + +.live-panel-card .card-body { + padding: 1.1rem 1.2rem 1.25rem; +} + +.live-panel-card--events .card-body { + max-height: 430px; + overflow: auto; +} + +.live-panel-card--events ul { + margin-bottom: 0; +} + +.live-panel-card--events li { + border-bottom: 1px solid #eef2f5; + padding: 0.45rem 0; +} + +.live-panel-card--events li:last-child { + border-bottom: none; + padding-bottom: 0; +} + +.live-panel-card h5 { + margin-top: 1rem; +} + +.op-controls-row { + display: flex; + gap: 1rem; + align-items: center; + flex-wrap: wrap; + margin-bottom: 0.8rem; +} + +@media (min-width: 1400px) { + .live-summary-grid { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } +} + +@media (max-width: 991px) { + .live-summary-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .live-command-strip { + flex-direction: column; + gap: 0.35rem; + } +} + +@media (max-width: 575px) { + .live-summary-grid { + grid-template-columns: 1fr; + } + + .live-report-page { + font-size: 0.92rem; + } + + .app-banner { + align-items: stretch; + flex-direction: column; + } +} \ No newline at end of file diff --git a/man/close_live_session.Rd b/man/close_live_session.Rd new file mode 100644 index 000000000..bd98c5589 --- /dev/null +++ b/man/close_live_session.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/extendr-wrappers.R +\name{close_live_session} +\alias{close_live_session} +\title{Close a local live reporting session.} +\usage{ +close_live_session(session_id) +} +\arguments{ +\item{session_id}{Live session identifier.} +} +\value{ +NULL, invisibly. +} +\description{ +Close a local live reporting session. +} +\keyword{internal} diff --git a/man/load_pm_parse_fit_object.Rd b/man/load_pm_parse_fit_object.Rd new file mode 100644 index 000000000..e8ac7f166 --- /dev/null +++ b/man/load_pm_parse_fit_object.Rd @@ -0,0 +1,38 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/PM_parse.R +\name{load_pm_parse_fit_object} +\alias{load_pm_parse_fit_object} +\title{Parse Pmetrics output} +\usage{ +load_pm_parse_fit_object(path, fit = "fit.rds") +} +\arguments{ +\item{path}{The directory containing the output from the Rust-implementation of NPAG} + +\item{fit}{The relative path to a "fit.rds" file, which is normal output after a fit containing the data and model used.} + +\item{write}{If \code{TRUE} (default), saves the output as "PMout.Rdata" in the specified path.} +} +\value{ +The output of \code{PM_parse} is a list containing the following elements +\itemize{ +\item \strong{op} Written to the standard of \code{\link[=PM_op]{PM_op()}} +\item \strong{pop} Written to the standard of \code{\link[=PM_pop]{PM_pop()}} +\item \strong{post} Written to the standard of \code{\link[=PM_post]{PM_post()}} +\item \strong{cycles} Written to the standard of \code{\link[=PM_cycle]{PM_cycle()}} +\item \strong{final} Written to the standard of \code{\link[=PM_final]{PM_final()}} +\item \strong{cov} Written to the standard of \code{\link[=PM_cov]{PM_cov()}} +} +} +\description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} + +A flexible parser for Pmetrics output +} +\details{ +Currently written for the Rust implementation of NPAG +} +\author{ +Michael Neely and Markus Hovd +} +\keyword{internal} diff --git a/man/publish_live_report_failed.Rd b/man/publish_live_report_failed.Rd new file mode 100644 index 000000000..4ac227dbb --- /dev/null +++ b/man/publish_live_report_failed.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/extendr-wrappers.R +\name{publish_live_report_failed} +\alias{publish_live_report_failed} +\title{Publish a finished report handoff failure to a live reporting session.} +\usage{ +publish_live_report_failed(session_id, message) +} +\arguments{ +\item{session_id}{Live session identifier.} + +\item{message}{Failure message.} +} +\value{ +NULL, invisibly. +} +\description{ +Publish a finished report handoff failure to a live reporting session. +} +\keyword{internal} diff --git a/man/publish_live_report_result.Rd b/man/publish_live_report_result.Rd new file mode 100644 index 000000000..c602aaf6b --- /dev/null +++ b/man/publish_live_report_result.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/extendr-wrappers.R +\name{publish_live_report_result} +\alias{publish_live_report_result} +\title{Publish a finished report payload to a live reporting session.} +\usage{ +publish_live_report_result(session_id, result_payload, report_generated_at) +} +\arguments{ +\item{session_id}{Live session identifier.} + +\item{result_payload}{Serialized report payload.} + +\item{report_generated_at}{Report timestamp.} +} +\value{ +NULL, invisibly. +} +\description{ +Publish a finished report payload to a live reporting session. +} +\keyword{internal} diff --git a/man/start_live_session.Rd b/man/start_live_session.Rd new file mode 100644 index 000000000..42e0c760a --- /dev/null +++ b/man/start_live_session.Rd @@ -0,0 +1,15 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/extendr-wrappers.R +\name{start_live_session} +\alias{start_live_session} +\title{Start a local live reporting session.} +\usage{ +start_live_session() +} +\value{ +Live session connection info. +} +\description{ +Start a local live reporting session. +} +\keyword{internal} diff --git a/man/wait_live_session_connected.Rd b/man/wait_live_session_connected.Rd new file mode 100644 index 000000000..745164100 --- /dev/null +++ b/man/wait_live_session_connected.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/extendr-wrappers.R +\name{wait_live_session_connected} +\alias{wait_live_session_connected} +\title{Wait for a live reporting client to connect.} +\usage{ +wait_live_session_connected(session_id, timeout_ms) +} +\arguments{ +\item{session_id}{Live session identifier.} + +\item{timeout_ms}{Maximum wait in milliseconds.} +} +\value{ +TRUE when a client connects before the timeout. +} +\description{ +Wait for a live reporting client to connect. +} +\keyword{internal} From 997cbf328273c55badffc25c0e7d067738d477c9 Mon Sep 17 00:00:00 2001 From: Michael Neely Date: Fri, 29 May 2026 21:40:03 -0700 Subject: [PATCH 6/7] wip: update documentation --- NAMESPACE | 8 +- R/PM_live.R | 34 ++- man/PM_cov.Rd | 227 +++++++++--------- man/PM_cycle.Rd | 200 ++++++++-------- man/PM_data.Rd | 378 +++++++++++++++--------------- man/PM_final.Rd | 203 ++++++++-------- man/PM_fit.Rd | 180 ++++++++------- man/PM_load.Rd | 6 +- man/PM_model.Rd | 498 +++++++++++++++++++--------------------- man/PM_op.Rd | 223 +++++++++--------- man/PM_opt.Rd | 174 +++++++------- man/PM_parse.Rd | 38 --- man/PM_pop.Rd | 195 ++++++++-------- man/PM_post.Rd | 195 ++++++++-------- man/PM_pri.Rd | 97 ++++---- man/PM_pta.Rd | 250 ++++++++++---------- man/PM_result.Rd | 536 +++++++++++++++++++++++-------------------- man/PM_sim.Rd | 401 ++++++++++++++++---------------- man/PM_valid.Rd | 226 +++++++++--------- man/mic1.Rd | 2 +- man/plot.PM_cov.Rd | 28 +-- man/plot.PM_cycle.Rd | 26 +-- man/plot.PM_data.Rd | 24 +- man/plot.PM_final.Rd | 28 +-- man/plot.PM_model.Rd | 24 +- man/plot.PM_op.Rd | 28 +-- man/plot.PM_opt.Rd | 24 +- man/plot.PM_pop.Rd | 24 +- man/plot.PM_post.Rd | 24 +- man/plot.PM_pta.Rd | 36 +-- man/plot.PM_sim.Rd | 28 +-- man/plot.PM_valid.Rd | 24 +- man/plot.PMvalid.Rd | 2 +- man/round2.Rd | 2 +- 34 files changed, 2204 insertions(+), 2189 deletions(-) delete mode 100644 man/PM_parse.Rd diff --git a/NAMESPACE b/NAMESPACE index 8b6018717..f967677e7 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -60,7 +60,6 @@ export(PM_manual) export(PM_model) export(PM_op) export(PM_opt) -export(PM_parse) export(PM_pop) export(PM_post) export(PM_pta) @@ -102,8 +101,8 @@ export(check_updates) export(clear_build) export(cli_ask) export(cli_df) -export(close_live_session) export(click_plot) +export(close_live_session) export(cor2cov) export(create_pmetrics_project) export(downloadR) @@ -118,6 +117,7 @@ export(interp) export(is_cargo_installed) export(latestR) export(lit_sim) +export(load_pm_parse_fit_object) export(makeAUC) export(makeErrorPoly) export(makeNCA) @@ -140,16 +140,18 @@ export(plotlygg) export(pm_plot) export(pos_def) export(proportional) +export(publish_live_report_failed) +export(publish_live_report_result) export(qgrowth) export(rgba_to_rgb) export(round2) export(save_flextable) export(setPMoptions) export(setup_logs) -export(start_live_session) export(simulate_all) export(simulate_one) export(ss.PK) +export(start_live_session) export(sub_plot) export(template) export(three_comp_bolus) diff --git a/R/PM_live.R b/R/PM_live.R index 2774f8022..4a218b140 100644 --- a/R/PM_live.R +++ b/R/PM_live.R @@ -30,6 +30,15 @@ pmetrics_live_app_dir <- function() { found[[1]] } +pmetrics_is_dev_package_path <- function(path) { + if (is.null(path) || !nzchar(path)) { + return(FALSE) + } + + file.exists(file.path(path, "DESCRIPTION")) && + file.exists(file.path(path, "R")) +} + launch_live_report_app <- function(live_session, launch.browser = TRUE, timeout_seconds = 30) { app_dir <- pmetrics_live_app_dir() if (!nzchar(app_dir)) { @@ -38,6 +47,7 @@ launch_live_report_app <- function(live_session, launch.browser = TRUE, timeout_ "i" = "Expected app files under {.path inst/apps/live-status}." )) } + app_dir <- normalizePath(app_dir, mustWork = TRUE) launch_dir <- tempfile("Pmetrics-live-status-") dir.create(launch_dir, recursive = TRUE, showWarnings = FALSE) @@ -50,10 +60,26 @@ launch_live_report_app <- function(live_session, launch.browser = TRUE, timeout_ process <- callr::r_bg( function(pkg_path, app_dir, port_file, live_session) { - if (nzchar(pkg_path) && file.exists(file.path(pkg_path, "DESCRIPTION")) && requireNamespace("pkgload", quietly = TRUE)) { - pkgload::load_all(pkg_path, quiet = TRUE, export_all = TRUE) - } else { - library(Pmetrics) + # In development checkouts, load local code. In installed environments, + # load the installed package to avoid pkgload-only assumptions. + loaded <- FALSE + if ( + nzchar(pkg_path) && + file.exists(file.path(pkg_path, "DESCRIPTION")) && + file.exists(file.path(pkg_path, "R")) && + requireNamespace("pkgload", quietly = TRUE) + ) { + loaded <- tryCatch( + { + pkgload::load_all(pkg_path, quiet = TRUE, export_all = TRUE) + TRUE + }, + error = function(e) FALSE + ) + } + + if (!isTRUE(loaded)) { + loadNamespace("Pmetrics") } options(Pmetrics.live_session = unclass(live_session)) diff --git a/man/PM_cov.Rd b/man/PM_cov.Rd index d91158d97..896f47891 100755 --- a/man/PM_cov.Rd +++ b/man/PM_cov.Rd @@ -36,9 +36,9 @@ parameter, covariate-time, or parameter-time relationships. Michael Neely, Julian Otalvaro } \section{Public fields}{ -\if{html}{\out{
}} -\describe{ -\item{\code{data}}{A data frame with the following columns + \if{html}{\out{
}} + \describe{ + \item{\code{data}}{A data frame with the following columns \itemize{ \item id Subject identification \item time Times of covariate observations @@ -48,148 +48,153 @@ for each subject, replicated as necessary for covariate observation times and du parameter means and medians \item icen The type of summarized Bayesian posterior individual parameter values: mean or median }} -} -\if{html}{\out{
}} + } + \if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ -\itemize{ -\item \href{#method-PM_cov-new}{\code{PM_cov$new()}} -\item \href{#method-PM_cov-step}{\code{PM_cov$step()}} -\item \href{#method-PM_cov-summary}{\code{PM_cov$summary()}} -\item \href{#method-PM_cov-plot}{\code{PM_cov$plot()}} -\item \href{#method-PM_cov-print}{\code{PM_cov$print()}} -\item \href{#method-PM_cov-clone}{\code{PM_cov$clone()}} -} + \itemize{ + \item \href{#method-PM_cov-initialize}{\code{PM_cov$new()}} + \item \href{#method-PM_cov-step}{\code{PM_cov$step()}} + \item \href{#method-PM_cov-summary}{\code{PM_cov$summary()}} + \item \href{#method-PM_cov-plot}{\code{PM_cov$plot()}} + \item \href{#method-PM_cov-print}{\code{PM_cov$print()}} + \item \href{#method-PM_cov-clone}{\code{PM_cov$clone()}} + } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_cov-new}{}}} -\subsection{Method \code{new()}}{ -Create new object populated with covariate-parameter information -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_cov$new(PMdata = NULL, path = ".", ...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{PMdata}}{include Saved, parsed output of prior run, used when source files are not available. +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_cov-initialize}{}}} +\subsection{\code{PM_cov$new()}}{ + Create new object populated with covariate-parameter information + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_cov$new(PMdata = NULL, path = ".", ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{PMdata}}{include Saved, parsed output of prior run, used when source files are not available. .} - -\item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. + \item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. .} - -\item{\code{...}}{Not currently used.} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -Creation of new \code{PM_cov} object is automatic and not generally necessary + \item{\code{...}}{Not currently used.} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + Creation of new \code{PM_cov} object is automatic and not generally necessary for the user to do. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_cov-step}{}}} -\subsection{Method \code{step()}}{ -Stepwise linear regression of covariates and Bayesian posterior +\subsection{\code{PM_cov$step()}}{ + Stepwise linear regression of covariates and Bayesian posterior parameter values -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_cov$step(...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{PM_step}} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -See \link{PM_step}. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_cov$step(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{PM_step}} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + See \link{PM_step}. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_cov-summary}{}}} -\subsection{Method \code{summary()}}{ -Summary method -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_cov$summary(...)}\if{html}{\out{
}} +\subsection{\code{PM_cov$summary()}}{ + Summary method + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_cov$summary(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{summary.PM_cov}} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + See \link{summary.PM_cov}. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{summary.PM_cov}} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -See \link{summary.PM_cov}. -} - -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_cov-plot}{}}} -\subsection{Method \code{plot()}}{ -Plot method -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_cov$plot(...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{plot.PM_cov}} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -See \link{plot.PM_cov}. +\subsection{\code{PM_cov$plot()}}{ + Plot method + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_cov$plot(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{plot.PM_cov}} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + See \link{plot.PM_cov}. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_cov-print}{}}} -\subsection{Method \code{print()}}{ -Print method -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_cov$print(...)}\if{html}{\out{
}} +\subsection{\code{PM_cov$print()}}{ + Print method + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_cov$print(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{print}} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + Print method for \link{PM_cov} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{print}} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -Print method for \link{PM_cov} -} - -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_cov-clone}{}}} -\subsection{Method \code{clone()}}{ -The objects of this class are cloneable with this method. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_cov$clone(deep = FALSE)}\if{html}{\out{
}} +\subsection{\code{PM_cov$clone()}}{ + The objects of this class are cloneable with this method. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_cov$clone(deep = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{deep}}{Whether to make a deep clone.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{deep}}{Whether to make a deep clone.} -} -\if{html}{\out{
}} -} -} } diff --git a/man/PM_cycle.Rd b/man/PM_cycle.Rd index 4b228253c..d4fa2a402 100755 --- a/man/PM_cycle.Rd +++ b/man/PM_cycle.Rd @@ -34,9 +34,9 @@ allows you to access them in an S3 way, e.g. \code{run1$cycle$mean} if \code{run Michael Neely, Julian Otalvaro } \section{Public fields}{ -\if{html}{\out{
}} -\describe{ -\item{\code{data}}{A list with the following elements, which can also be extracted by name. + \if{html}{\out{
}} + \describe{ + \item{\code{data}}{A list with the following elements, which can also be extracted by name. e.e. \code{run1$cycle$objective}, which is equivalent to \code{run1$cycle$data$objective}. \strong{names} Vector of names of the random parameters \strong{objective} A tibble of -2*Log-likelihood, AIC and BIC at each cycle @@ -45,140 +45,144 @@ e.e. \code{run1$cycle$objective}, which is equivalent to \code{run1$cycle$data$o \strong{median} A tibble of cycle number and the median of each random parameter at each cycle, normalized to initial median \strong{sd} A tibble of cycle number and the standard deviation of each random parameter at each cycle, normalized to initial standard deviation \strong{status} Status of the last cycle: "Converged", "Maximum cycles reached", or "Posterior".} -} -\if{html}{\out{
}} + } + \if{html}{\out{
}} } \section{Active bindings}{ -\if{html}{\out{
}} -\describe{ -\item{\code{names}}{Vector of names of the random parameters} + \if{html}{\out{
}} + \describe{ + \item{\code{names}}{Vector of names of the random parameters} -\item{\code{objective}}{A tibble of -2*Log-likelihood, AIC and BIC at each cycle} + \item{\code{objective}}{A tibble of -2*Log-likelihood, AIC and BIC at each cycle} -\item{\code{gamlam}}{A tibble of cycle number and gamma or lambda at each cycle for each output equation} + \item{\code{gamlam}}{A tibble of cycle number and gamma or lambda at each cycle for each output equation} -\item{\code{mean}}{A tibble of cycle number and the mean of each random parameter + \item{\code{mean}}{A tibble of cycle number and the mean of each random parameter at each cycle, normalized to initial mean} -\item{\code{median}}{A tibble of cycle number and the median of each random + \item{\code{median}}{A tibble of cycle number and the median of each random parameter at each cycle, normalized to initial median} -\item{\code{sd}}{A tibble of cycle number and the standard deviation of each random parameter + \item{\code{sd}}{A tibble of cycle number and the standard deviation of each random parameter at each cycle, normalized to initial standard deviation} -\item{\code{status}}{Status of the last cycle: "Converged", "Maximum cycles reached", or "Posterior"} -} -\if{html}{\out{
}} + \item{\code{status}}{Status of the last cycle: "Converged", "Maximum cycles reached", or "Posterior"} + } + \if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ -\itemize{ -\item \href{#method-PM_cycle-new}{\code{PM_cycle$new()}} -\item \href{#method-PM_cycle-plot}{\code{PM_cycle$plot()}} -\item \href{#method-PM_cycle-summary}{\code{PM_cycle$summary()}} -\item \href{#method-PM_cycle-print}{\code{PM_cycle$print()}} -\item \href{#method-PM_cycle-clone}{\code{PM_cycle$clone()}} -} + \itemize{ + \item \href{#method-PM_cycle-initialize}{\code{PM_cycle$new()}} + \item \href{#method-PM_cycle-plot}{\code{PM_cycle$plot()}} + \item \href{#method-PM_cycle-summary}{\code{PM_cycle$summary()}} + \item \href{#method-PM_cycle-print}{\code{PM_cycle$print()}} + \item \href{#method-PM_cycle-clone}{\code{PM_cycle$clone()}} + } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_cycle-new}{}}} -\subsection{Method \code{new()}}{ -Create new object populated with cycle information -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_cycle$new(PMdata = NULL, path = ".", ...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{PMdata}}{include Saved, parsed output of prior run, used when source files are not available. +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_cycle-initialize}{}}} +\subsection{\code{PM_cycle$new()}}{ + Create new object populated with cycle information + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_cycle$new(PMdata = NULL, path = ".", ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{PMdata}}{include Saved, parsed output of prior run, used when source files are not available. .} - -\item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. + \item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. .} - -\item{\code{...}}{Not currently used.} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -Creation of new \code{PM_cycle} object is automatic and not generally necessary + \item{\code{...}}{Not currently used.} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + Creation of new \code{PM_cycle} object is automatic and not generally necessary for the user to do. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_cycle-plot}{}}} -\subsection{Method \code{plot()}}{ -Plot method -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_cycle$plot(...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{plot.PM_cycle}} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -See \link{plot.PM_cycle}. +\subsection{\code{PM_cycle$plot()}}{ + Plot method + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_cycle$plot(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{plot.PM_cycle}} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + See \link{plot.PM_cycle}. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_cycle-summary}{}}} -\subsection{Method \code{summary()}}{ -Summary method -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_cycle$summary(...)}\if{html}{\out{
}} +\subsection{\code{PM_cycle$summary()}}{ + Summary method + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_cycle$summary(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{summary.PM_cycle}} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + See \link{summary.PM_cycle}. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{summary.PM_cycle}} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -See \link{summary.PM_cycle}. -} - -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_cycle-print}{}}} -\subsection{Method \code{print()}}{ -Print method -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_cycle$print()}\if{html}{\out{
}} +\subsection{\code{PM_cycle$print()}}{ + Print method + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_cycle$print()} + \if{html}{\out{
}} + } + \subsection{Details}{ + Prints the last cycle summary information. + } } -\subsection{Details}{ -Prints the last cycle summary information. -} - -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_cycle-clone}{}}} -\subsection{Method \code{clone()}}{ -The objects of this class are cloneable with this method. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_cycle$clone(deep = FALSE)}\if{html}{\out{
}} +\subsection{\code{PM_cycle$clone()}}{ + The objects of this class are cloneable with this method. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_cycle$clone(deep = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{deep}}{Whether to make a deep clone.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{deep}}{Whether to make a deep clone.} -} -\if{html}{\out{
}} -} -} } diff --git a/man/PM_data.Rd b/man/PM_data.Rd index dc1f131b3..7d21fc9bc 100755 --- a/man/PM_data.Rd +++ b/man/PM_data.Rd @@ -35,7 +35,7 @@ is particularly useful for making simulation templates on the fly. \examples{ ## ------------------------------------------------ -## Method `PM_data$addEvent` +## Method `PM_data$addEvent()` ## ------------------------------------------------ \dontrun{ @@ -44,53 +44,53 @@ out = NA, wt = 75)$addEvent(id = 1, time = 60, out = -1) } } \section{Public fields}{ -\if{html}{\out{
}} -\describe{ -\item{\code{data}}{Data frame containing the data to be modeled} + \if{html}{\out{
}} + \describe{ + \item{\code{data}}{Data frame containing the data to be modeled} -\item{\code{standard_data}}{Data frame containing standardized version of the data} + \item{\code{standard_data}}{Data frame containing standardized version of the data} -\item{\code{pop}}{The \verb{$data} field from a \link{PM_pop} object. This makes it easy to add population predictions to a raw data plot. This field will be \code{NULL} until the \link{PM_data} object is added to the \link{PM_result} after a run. As examples: + \item{\code{pop}}{The \verb{$data} field from a \link{PM_pop} object. This makes it easy to add population predictions to a raw data plot. This field will be \code{NULL} until the \link{PM_data} object is added to the \link{PM_result} after a run. As examples: \itemize{ \item \code{dat <- PM_data$new("data.csv")}. Here, \code{dat$pop} will be \code{NULL}. \item \code{run1 <- PM_load(1)}. Here, \code{run1$data$pop} will be the same as \code{run1$pop$data}. }} -\item{\code{post}}{The \verb{$data} field from a \link{PM_post} object. See details in the \code{pop} argument above.} -} -\if{html}{\out{
}} + \item{\code{post}}{The \verb{$data} field from a \link{PM_post} object. See details in the \code{pop} argument above.} + } + \if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ -\itemize{ -\item \href{#method-PM_data-new}{\code{PM_data$new()}} -\item \href{#method-PM_data-save}{\code{PM_data$save()}} -\item \href{#method-PM_data-auc}{\code{PM_data$auc()}} -\item \href{#method-PM_data-nca}{\code{PM_data$nca()}} -\item \href{#method-PM_data-plot}{\code{PM_data$plot()}} -\item \href{#method-PM_data-print}{\code{PM_data$print()}} -\item \href{#method-PM_data-summary}{\code{PM_data$summary()}} -\item \href{#method-PM_data-addEvent}{\code{PM_data$addEvent()}} -\item \href{#method-PM_data-clone}{\code{PM_data$clone()}} -} + \itemize{ + \item \href{#method-PM_data-initialize}{\code{PM_data$new()}} + \item \href{#method-PM_data-save}{\code{PM_data$save()}} + \item \href{#method-PM_data-auc}{\code{PM_data$auc()}} + \item \href{#method-PM_data-nca}{\code{PM_data$nca()}} + \item \href{#method-PM_data-plot}{\code{PM_data$plot()}} + \item \href{#method-PM_data-print}{\code{PM_data$print()}} + \item \href{#method-PM_data-summary}{\code{PM_data$summary()}} + \item \href{#method-PM_data-addEvent}{\code{PM_data$addEvent()}} + \item \href{#method-PM_data-clone}{\code{PM_data$clone()}} + } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_data-new}{}}} -\subsection{Method \code{new()}}{ -Create new data object -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_data$new(data = NULL, dt = NULL, quiet = FALSE, validate = TRUE, ...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{data}}{A quoted name of a file with full path if not +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_data-initialize}{}}} +\subsection{\code{PM_data$new()}}{ + Create new data object + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_data$new(data = NULL, dt = NULL, quiet = FALSE, validate = TRUE, ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{data}}{A quoted name of a file with full path if not in the working directory, an unquoted name of a data frame in the current R environment, or a \link{PM_data} object, which will rebuild it.} - -\item{\code{dt}}{Pmetrics will try a variety of date/time formats. If all 16 of + \item{\code{dt}}{Pmetrics will try a variety of date/time formats. If all 16 of them fail, use this parameter to specify the correct format as a character vector whose first element is date format and second is time. Use the following abbreviations: @@ -106,175 +106,175 @@ Always check to make sure that dates/times were parsed correctly and the relative times in the \code{PM_data$standard_data} field look correct. Other date/time formats are possible. See \code{\link[lubridate:parse_date_time]{lubridate::parse_date_time()}} for these. }} - -\item{\code{quiet}}{Quietly validate. Default is \code{FALSE}.} - -\item{\code{validate}}{Check for errors. Default is \code{TRUE}. Strongly recommended.} - -\item{\code{...}}{Other arguments (not currently used).} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -Creation of a new \link{PM_data} objects from a file or + \item{\code{quiet}}{Quietly validate. Default is \code{FALSE}.} + \item{\code{validate}}{Check for errors. Default is \code{TRUE}. Strongly recommended.} + \item{\code{...}}{Other arguments (not currently used).} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + Creation of a new \link{PM_data} objects from a file or a data frame. Data will be standardized and checked automatically to a fully specified, valid data object. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_data-save}{}}} -\subsection{Method \code{save()}}{ -Save data to file -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_data$save(file_name, ...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{file_name}}{A quoted name of the file to create +\subsection{\code{PM_data$save()}}{ + Save data to file + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_data$save(file_name, ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{file_name}}{A quoted name of the file to create with full path if not in the working directory.} - -\item{\code{...}}{Arguments passed to \link{PMwriteMatrix}} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -Saves a delimited file (e.g. comma-separated) + \item{\code{...}}{Arguments passed to \link{PMwriteMatrix}} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + Saves a delimited file (e.g. comma-separated) from the \code{standard_data} field + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_data-auc}{}}} -\subsection{Method \code{auc()}}{ -Calculate AUC -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_data$auc(...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{make_AUC}.} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -See \link{make_AUC}. +\subsection{\code{PM_data$auc()}}{ + Calculate AUC + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_data$auc(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{make_AUC}.} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + See \link{make_AUC}. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_data-nca}{}}} -\subsection{Method \code{nca()}}{ -Perform non-compartmental analysis -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_data$nca(...)}\if{html}{\out{
}} +\subsection{\code{PM_data$nca()}}{ + Perform non-compartmental analysis + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_data$nca(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{make_NCA}.} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + See \link{make_NCA}. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{make_NCA}.} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -See \link{make_NCA}. -} - -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_data-plot}{}}} -\subsection{Method \code{plot()}}{ -Plot method -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_data$plot(...)}\if{html}{\out{
}} +\subsection{\code{PM_data$plot()}}{ + Plot method + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_data$plot(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{plot.PM_data}} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + See \link{plot.PM_data}. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{plot.PM_data}} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -See \link{plot.PM_data}. -} - -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_data-print}{}}} -\subsection{Method \code{print()}}{ -Print method -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_data$print(standard = F, viewer = T, ...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{standard}}{Display the standardized data if \code{TRUE}. +\subsection{\code{PM_data$print()}}{ + Print method + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_data$print(standard = F, viewer = T, ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{standard}}{Display the standardized data if \code{TRUE}. Default is \code{FALSE}.} - -\item{\code{viewer}}{Display the Viewer if \code{TRUE}. + \item{\code{viewer}}{Display the Viewer if \code{TRUE}. Default is \code{TRUE}.} - -\item{\code{...}}{Other arguments to \link{print.data.frame}. Only + \item{\code{...}}{Other arguments to \link{print.data.frame}. Only passed if \code{viewer = FALSE}.} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -Displays the PM_data object in a variety of ways. + } + \if{html}{\out{
}} + } + \subsection{Details}{ + Displays the PM_data object in a variety of ways. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_data-summary}{}}} -\subsection{Method \code{summary()}}{ -Summary method -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_data$summary(...)}\if{html}{\out{
}} +\subsection{\code{PM_data$summary()}}{ + Summary method + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_data$summary(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{summary.PM_data}.} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + See \link{summary.PM_data}. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{summary.PM_data}.} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -See \link{summary.PM_data}. -} - -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_data-addEvent}{}}} -\subsection{Method \code{addEvent()}}{ -Add events to PM_data object -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_data$addEvent(..., dt = NULL, quiet = FALSE, validate = FALSE)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Column names and values.} - -\item{\code{dt}}{Pmetrics will try a variety of date/time formats. If all 16 of +\subsection{\code{PM_data$addEvent()}}{ + Add events to PM_data object + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_data$addEvent(..., dt = NULL, quiet = FALSE, validate = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Column names and values.} + \item{\code{dt}}{Pmetrics will try a variety of date/time formats. If all 16 of them fail, use this parameter to specify the correct format as a character vector whose first element is date format and second is time. Use the following abbreviations: @@ -290,19 +290,17 @@ Always check to make sure that dates/times were parsed correctly and the relative times in the \code{PM_data$standard_data} field look correct. Other date/time formats are possible. See \code{\link[lubridate:parse_date_time]{lubridate::parse_date_time()}} for these. }} - -\item{\code{quiet}}{Quietly validate. Default is \code{FALSE}.} - -\item{\code{validate}}{Validate the new row or not. Default is \code{FALSE} as a new row + \item{\code{quiet}}{Quietly validate. Default is \code{FALSE}.} + \item{\code{validate}}{Validate the new row or not. Default is \code{FALSE} as a new row added to a blank will result in a one-row data object, which is invalid. Also, only one event type (dose or observation) should be added at a time, so if the new object contains only doses while building, this would cause an error. You should set \code{validate = TRUE} for the final addition.} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -Add lines to a PM_data object by supplying named columns and values. + } + \if{html}{\out{
}} + } + \subsection{Details}{ + Add lines to a PM_data object by supplying named columns and values. \code{ID} is always required. \code{Time} is handled differently depending on the sequence of \code{addEvent} calls (see \strong{Chaining} below). \itemize{ @@ -335,35 +333,33 @@ As we have seen in the examples above, \code{ADDL} and \code{II} are supported. to create a blank data object and then add rows. This can be particularly useful for creating simulation templates. See the example. -} - -\subsection{Examples}{ -\if{html}{\out{
}} -\preformatted{\dontrun{ -PM_data$new()$addEvent(id = 1, time = 0, dose = 100, addl = 4, ii = 12, + } + \subsection{Examples}{ + \if{html}{\out{
}} + \preformatted{PM_data$new()$addEvent(id = 1, time = 0, dose = 100, addl = 4, ii = 12, out = NA, wt = 75)$addEvent(id = 1, time = 60, out = -1) } -} -\if{html}{\out{
}} - + \if{html}{\out{
}} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_data-clone}{}}} -\subsection{Method \code{clone()}}{ -The objects of this class are cloneable with this method. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_data$clone(deep = FALSE)}\if{html}{\out{
}} +\subsection{\code{PM_data$clone()}}{ + The objects of this class are cloneable with this method. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_data$clone(deep = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{deep}}{Whether to make a deep clone.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{deep}}{Whether to make a deep clone.} -} -\if{html}{\out{
}} -} -} } diff --git a/man/PM_final.Rd b/man/PM_final.Rd index 3500b1e0c..b3812e005 100755 --- a/man/PM_final.Rd +++ b/man/PM_final.Rd @@ -34,9 +34,9 @@ allows you to access them in an S3 way, e.g. \code{run1$final$popMean} if \code{ Michael Neely, Julian Otalvaro } \section{Public fields}{ -\if{html}{\out{
}} -\describe{ -\item{\code{data}}{A list with the following elements, which can also be extracted by name. + \if{html}{\out{
}} + \describe{ + \item{\code{data}}{A list with the following elements, which can also be extracted by name. \itemize{ \item \strong{popPoints} (NPAG only) Data frame of the final cycle joint population density of grid points with column names equal to the name of each random parameter plus \emph{prob} for the @@ -70,55 +70,55 @@ the medians of the posterior distributions for each parameter. \item \strong{nsub} Number of subjects \item \strong{ab} Tibble/data frame of boundaries for random parameter values with columns: name, lower, upper. }} -} -\if{html}{\out{
}} + } + \if{html}{\out{
}} } \section{Active bindings}{ -\if{html}{\out{
}} -\describe{ -\item{\code{popPoints}}{(NPAG only) Data frame of the final cycle joint population density of grid points + \if{html}{\out{
}} + \describe{ + \item{\code{popPoints}}{(NPAG only) Data frame of the final cycle joint population density of grid points with column names equal to the name of each random parameter plus \emph{prob} for the associated probability of that point} -\item{\code{popMean}}{The final cycle mean for each random parameter distribution} + \item{\code{popMean}}{The final cycle mean for each random parameter distribution} -\item{\code{popSD}}{The final cycle standard deviation for each random parameter distribution} + \item{\code{popSD}}{The final cycle standard deviation for each random parameter distribution} -\item{\code{popCV}}{The final cycle coefficient of variation (SD/Mean) for each random parameter distribution} + \item{\code{popCV}}{The final cycle coefficient of variation (SD/Mean) for each random parameter distribution} -\item{\code{popVar}}{The final cycle variance for each random parameter distribution} + \item{\code{popVar}}{The final cycle variance for each random parameter distribution} -\item{\code{popCov}}{The final cycle random parameter covariance matrix} + \item{\code{popCov}}{The final cycle random parameter covariance matrix} -\item{\code{popCor}}{The final cycle random parameter correlation matrix} + \item{\code{popCor}}{The final cycle random parameter correlation matrix} -\item{\code{popMed}}{The final cycle median values for each random parameter, + \item{\code{popMed}}{The final cycle median values for each random parameter, i.e. those that have unknown mean and unknown variance, both of which are fitted during the run} -\item{\code{postPoints}}{(NPAG only) Data frame of posterior population points for each of the first 100 subject, + \item{\code{postPoints}}{(NPAG only) Data frame of posterior population points for each of the first 100 subject, with columns id, point, parameters and probability. The first column is the subject, the second column has the population point number, followed by the values for the parameters in that point and the probability.} -\item{\code{postMean}}{A \emph{nsub} x \emph{npar} data frame containing + \item{\code{postMean}}{A \emph{nsub} x \emph{npar} data frame containing the means of the posterior distributions for each parameter.} -\item{\code{postSD}}{A \emph{nsub} x \emph{npar} data frame containing + \item{\code{postSD}}{A \emph{nsub} x \emph{npar} data frame containing the SDs of the posterior distributions for each parameter.} -\item{\code{postVar}}{A \emph{nsub} x \emph{npar} data frame containing + \item{\code{postVar}}{A \emph{nsub} x \emph{npar} data frame containing the variances of the posterior distributions for each parameter.} -\item{\code{postCov}}{NPAG only: An list of length \emph{nsub}, each element with an \emph{npar} x \emph{npar} data frame + \item{\code{postCov}}{NPAG only: An list of length \emph{nsub}, each element with an \emph{npar} x \emph{npar} data frame that contains the posterior parameter value covariances for that subject.} -\item{\code{postCor}}{NPAG only: An list of length \emph{nsub}, each element with an \emph{npar} x \emph{npar} data frame + \item{\code{postCor}}{NPAG only: An list of length \emph{nsub}, each element with an \emph{npar} x \emph{npar} data frame that contains the posterior parameter value correlations for that subject.} -\item{\code{postMed}}{A \emph{nsub} x \emph{npar} data frame containing + \item{\code{postMed}}{A \emph{nsub} x \emph{npar} data frame containing the medians of the posterior distributions for each parameter.*} -\item{\code{shrinkage}}{A data frame with the shrinkage for each parameter. + \item{\code{shrinkage}}{A data frame with the shrinkage for each parameter. The total population variance for a parameter is comprised of variance(EBE) plus average variance(EBD), where each subject's EBE is the Empirical Bayes Estimate or mean posterior value @@ -156,108 +156,111 @@ In contrast, badly undersampled subjects can result in only one support point. There is no formal criterion for this statistic, but it can be used in combination with shrinkage to assess the information content of the data.} -\item{\code{gridpts}}{(NPAG only) Initial number of support points} + \item{\code{gridpts}}{(NPAG only) Initial number of support points} -\item{\code{nsub}}{Number of subjects} + \item{\code{nsub}}{Number of subjects} -\item{\code{ab}}{Matrix of boundaries for random parameter values} -} -\if{html}{\out{
}} + \item{\code{ab}}{Matrix of boundaries for random parameter values} + } + \if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ -\itemize{ -\item \href{#method-PM_final-new}{\code{PM_final$new()}} -\item \href{#method-PM_final-plot}{\code{PM_final$plot()}} -\item \href{#method-PM_final-summary}{\code{PM_final$summary()}} -\item \href{#method-PM_final-clone}{\code{PM_final$clone()}} -} + \itemize{ + \item \href{#method-PM_final-initialize}{\code{PM_final$new()}} + \item \href{#method-PM_final-plot}{\code{PM_final$plot()}} + \item \href{#method-PM_final-summary}{\code{PM_final$summary()}} + \item \href{#method-PM_final-clone}{\code{PM_final$clone()}} + } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_final-new}{}}} -\subsection{Method \code{new()}}{ -Create new object populated with final cycle information -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_final$new(PMdata = NULL, path = ".", ...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{PMdata}}{include Saved, parsed output of prior run, used when source files are not available. +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_final-initialize}{}}} +\subsection{\code{PM_final$new()}}{ + Create new object populated with final cycle information + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_final$new(PMdata = NULL, path = ".", ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{PMdata}}{include Saved, parsed output of prior run, used when source files are not available. .} - -\item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. + \item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. .} - -\item{\code{...}}{Not currently used.} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -Creation of new \code{PM_final} object is automatic and not generally necessary + \item{\code{...}}{Not currently used.} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + Creation of new \code{PM_final} object is automatic and not generally necessary for the user to do. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_final-plot}{}}} -\subsection{Method \code{plot()}}{ -Plot method -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_final$plot(...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{plot.PM_final}} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -See \link{plot.PM_final}. +\subsection{\code{PM_final$plot()}}{ + Plot method + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_final$plot(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{plot.PM_final}} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + See \link{plot.PM_final}. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_final-summary}{}}} -\subsection{Method \code{summary()}}{ -Summary method -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_final$summary(...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{summary.PM_final}} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -See \link{summary.PM_final}. +\subsection{\code{PM_final$summary()}}{ + Summary method + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_final$summary(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{summary.PM_final}} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + See \link{summary.PM_final}. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_final-clone}{}}} -\subsection{Method \code{clone()}}{ -The objects of this class are cloneable with this method. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_final$clone(deep = FALSE)}\if{html}{\out{
}} +\subsection{\code{PM_final$clone()}}{ + The objects of this class are cloneable with this method. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_final$clone(deep = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{deep}}{Whether to make a deep clone.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{deep}}{Whether to make a deep clone.} -} -\if{html}{\out{
}} -} -} } diff --git a/man/PM_fit.Rd b/man/PM_fit.Rd index bc5612aec..4d11b4348 100755 --- a/man/PM_fit.Rd +++ b/man/PM_fit.Rd @@ -15,133 +15,143 @@ methods to cross-check data and model objects for compatibility. } \keyword{internal} \section{Public fields}{ -\if{html}{\out{
}} -\describe{ -\item{\code{data}}{\link{PM_data} object} + \if{html}{\out{
}} + \describe{ + \item{\code{data}}{\link{PM_data} object} -\item{\code{model}}{\link{PM_model} object} -} -\if{html}{\out{
}} + \item{\code{model}}{\link{PM_model} object} + } + \if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ -\itemize{ -\item \href{#method-PM_fit-new}{\code{PM_fit$new()}} -\item \href{#method-PM_fit-run}{\code{PM_fit$run()}} -\item \href{#method-PM_fit-save}{\code{PM_fit$save()}} -\item \href{#method-PM_fit-load}{\code{PM_fit$load()}} -\item \href{#method-PM_fit-check}{\code{PM_fit$check()}} -\item \href{#method-PM_fit-clone}{\code{PM_fit$clone()}} -} + \itemize{ + \item \href{#method-PM_fit-initialize}{\code{PM_fit$new()}} + \item \href{#method-PM_fit-run}{\code{PM_fit$run()}} + \item \href{#method-PM_fit-save}{\code{PM_fit$save()}} + \item \href{#method-PM_fit-load}{\code{PM_fit$load()}} + \item \href{#method-PM_fit-check}{\code{PM_fit$check()}} + \item \href{#method-PM_fit-clone}{\code{PM_fit$clone()}} + } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_fit-new}{}}} -\subsection{Method \code{new()}}{ -Create a new object -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_fit$new(data = data, model = model)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{data}}{Either the name of a \link{PM_data} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_fit-initialize}{}}} +\subsection{\code{PM_fit$new()}}{ + Create a new object + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_fit$new(data = data, model = model)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{data}}{Either the name of a \link{PM_data} object in memory or the quoted name of a Pmetrics data file in the current working directory, which will crate a \link{PM_data} object on the fly. However, if created on the fly, this object will not be available to other methods or other instances of \link{PM_fit}.} - -\item{\code{model}}{Similarly, this is either the name of a \link{PM_model} + \item{\code{model}}{Similarly, this is either the name of a \link{PM_model} object in memory or the quoted name of a Pmetrics text model file in the current working directory. Again, if created on the fly, the object will not be available to other methods or other instances of \link{PM_fit}.} + } + \if{html}{\out{
}} + } } -\if{html}{\out{
}} -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_fit-run}{}}} -\subsection{Method \code{run()}}{ -Run a fit of model to the data (deprecated) -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_fit$run()}\if{html}{\out{
}} -} - -\subsection{Details}{ -The \verb{$run} method for \code{PM_fit} objects has been deprecated in +\subsection{\code{PM_fit$run()}}{ + Run a fit of model to the data (deprecated) + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_fit$run()} + \if{html}{\out{
}} + } + \subsection{Details}{ + The \verb{$run} method for \code{PM_fit} objects has been deprecated in favor of the \verb{$fit} method in \code{\link[=PM_model]{PM_model()}}. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_fit-save}{}}} -\subsection{Method \code{save()}}{ -Save the current PM_fit object to a .rds file. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_fit$save(file_name = "PMfit.rds")}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{file_name}}{Name of the file to be created. The +\subsection{\code{PM_fit$save()}}{ + Save the current PM_fit object to a .rds file. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_fit$save(file_name = "PMfit.rds")} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{file_name}}{Name of the file to be created. The default is "PMfit.rds".} + } + \if{html}{\out{
}} + } } -\if{html}{\out{
}} -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_fit-load}{}}} -\subsection{Method \code{load()}}{ -\code{PM_fit} objects contain a \code{save} method which invokes \link{saveRDS} to write +\subsection{\code{PM_fit$load()}}{ + \code{PM_fit} objects contain a \code{save} method which invokes \link{saveRDS} to write the object to the hard drive as an .rds file. This is the corresponding load function. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_fit$load(file_name)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_fit$load(file_name)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{file_name}}{Name of the file to be read, the default is "PMfit.rds".} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + A \code{PM_fit} object. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{file_name}}{Name of the file to be read, the default is "PMfit.rds".} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -A \code{PM_fit} object. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_fit-check}{}}} -\subsection{Method \code{check()}}{ -Checks for errors in data and model objects and agreement between them. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_fit$check()}\if{html}{\out{
}} +\subsection{\code{PM_fit$check()}}{ + Checks for errors in data and model objects and agreement between them. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_fit$check()} + \if{html}{\out{
}} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_fit-clone}{}}} -\subsection{Method \code{clone()}}{ -The objects of this class are cloneable with this method. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_fit$clone(deep = FALSE)}\if{html}{\out{
}} +\subsection{\code{PM_fit$clone()}}{ + The objects of this class are cloneable with this method. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_fit$clone(deep = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{deep}}{Whether to make a deep clone.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{deep}}{Whether to make a deep clone.} -} -\if{html}{\out{
}} -} -} } diff --git a/man/PM_load.Rd b/man/PM_load.Rd index bc09a0a89..7af4b9180 100755 --- a/man/PM_load.Rd +++ b/man/PM_load.Rd @@ -14,9 +14,7 @@ PM_load(run, path = ".", file = "PMout.Rdata") \item{file}{Default is "PMout.Rdata", which is created after a Pmetrics run, but it could also be the name of an .Rdata file created by running the -\verb{$save} method for a \link{PM_result} object. If the default snapshot is -missing, \code{PM_load()} will rebuild the result from the standard run outputs and -the saved \code{fit.rds} input snapshot when they are available.} +\verb{$save} method for a \link{PM_result} object.} } \value{ An R6 \link{PM_result}. @@ -33,8 +31,6 @@ A combination of \code{run}, \code{path}, and \code{file} are used to locate the \item If \code{run} is missing, the results are assumed to be in the \code{path} folder. \item The \code{file} name is the name of the Rdata file containing the results. Default is "PMout.Rdata", which is created by Pmetrics after a run. -If that default snapshot is missing, \code{PM_load()} rebuilds the result from the -standard run outputs and the saved \code{fit.rds} input snapshot when possible. \item If both \code{run} and \code{path} are missing, the current working directory is used for \code{path}. \item If both \code{run} and \code{file} are missing, the current working directory is used diff --git a/man/PM_model.Rd b/man/PM_model.Rd index 76024b546..395d3562c 100755 --- a/man/PM_model.Rd +++ b/man/PM_model.Rd @@ -74,37 +74,37 @@ mod <- PM_model$new(mod_list) Michael Neely } \section{Public fields}{ -\if{html}{\out{
}} -\describe{ -\item{\code{model_list}}{A list containing normalized model metadata derived from + \if{html}{\out{
}} + \describe{ + \item{\code{model_list}}{A list containing normalized model metadata derived from the original R model blocks} -\item{\code{arg_list}}{A list containing the original arguments passed to the model} -} -\if{html}{\out{
}} + \item{\code{arg_list}}{A list containing the original arguments passed to the model} + } + \if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ -\itemize{ -\item \href{#method-PM_model-new}{\code{PM_model$new()}} -\item \href{#method-PM_model-print}{\code{PM_model$print()}} -\item \href{#method-PM_model-plot}{\code{PM_model$plot()}} -\item \href{#method-PM_model-fit}{\code{PM_model$fit()}} -\item \href{#method-PM_model-map}{\code{PM_model$map()}} -\item \href{#method-PM_model-sim}{\code{PM_model$sim()}} -\item \href{#method-PM_model-dsl}{\code{PM_model$dsl()}} -\item \href{#method-PM_model-debug_dsl}{\code{PM_model$debug_dsl()}} -\item \href{#method-PM_model-compile}{\code{PM_model$compile()}} -\item \href{#method-PM_model-save}{\code{PM_model$save()}} -\item \href{#method-PM_model-copy}{\code{PM_model$copy()}} -\item \href{#method-PM_model-clone}{\code{PM_model$clone()}} -} + \itemize{ + \item \href{#method-PM_model-initialize}{\code{PM_model$new()}} + \item \href{#method-PM_model-print}{\code{PM_model$print()}} + \item \href{#method-PM_model-plot}{\code{PM_model$plot()}} + \item \href{#method-PM_model-fit}{\code{PM_model$fit()}} + \item \href{#method-PM_model-map}{\code{PM_model$map()}} + \item \href{#method-PM_model-sim}{\code{PM_model$sim()}} + \item \href{#method-PM_model-dsl}{\code{PM_model$dsl()}} + \item \href{#method-PM_model-debug_dsl}{\code{PM_model$debug_dsl()}} + \item \href{#method-PM_model-compile}{\code{PM_model$compile()}} + \item \href{#method-PM_model-save}{\code{PM_model$save()}} + \item \href{#method-PM_model-copy}{\code{PM_model$copy()}} + \item \href{#method-PM_model-clone}{\code{PM_model$clone()}} + } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_model-new}{}}} -\subsection{Method \code{new()}}{ -This is the method to create a new \code{PM_model} object. +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_model-initialize}{}}} +\subsection{\code{PM_model$new()}}{ + This is the method to create a new \code{PM_model} object. The first argument allows creation of a model from a variety of pre-existing sources, and if used, all the subsequent arguments will be ignored. If a model @@ -143,8 +143,9 @@ Again, all R code will be converted to lowercase prior to translation into Rust. } \strong{Important:} All models must have \code{pri}, \code{eqn}, \code{out}, and \code{err} blocks. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_model$new( + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_model$new( x = NULL, pri = NULL, cov = NULL, @@ -157,13 +158,13 @@ Again, all R code will be converted to lowercase prior to translation into Rust. err = NULL, solver = NULL, ... -)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{x}}{An optional argument, but if specified, all the subsequent +)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{x}}{An optional argument, but if specified, all the subsequent arguments will be ignored. \code{x} creates a \code{PM_model} from existing appropriate input, which can be one of the following: \itemize{ @@ -186,8 +187,7 @@ prior example: \code{PM_model$new(mod)} } See the user manual \code{\link[=PM_manual]{PM_manual()}} for more help on directly defining models in R.} - -\item{\code{pri}}{The first of the arguments used if \code{x} is not specified. This is + \item{\code{pri}}{The first of the arguments used if \code{x} is not specified. This is a named list of primary parameters, which are the model parameters that are estimated in the population analysis. They are specified by one of two creator functions: \code{\link[=ab]{ab()}} or \code{\link[=msd]{msd()}}. For example, @@ -201,8 +201,7 @@ by one of two creator functions: \code{\link[=ab]{ab()}} or \code{\link[=msd]{ms The \code{\link[=ab]{ab()}} creator specifies the initial range \verb{[a, b]} of the parameter, while the \code{\link[=msd]{msd()}} creator specifies the initial mean and standard deviation of the parameter.} - -\item{\code{cov}}{A list whose names are some or all of the covariates in the data file. + \item{\code{cov}}{A list whose names are some or all of the covariates in the data file. Unlike prior versions of Pmetrics, as of 3.0.0, they do not have to be listed in the same order as in the data file, and they do not need to be all present. \strong{Only those covariates you wish to use in model equations or analyze for relationships to model parameters need to be declared here.} @@ -222,8 +221,7 @@ For example: }\if{html}{\out{
}} Note that \code{wt = interp()} is equivalent to \code{wt = interp("lm")}, since "lm" is the default.} - -\item{\code{sec}}{A function defining the secondary (global) equations in the model. Values + \item{\code{sec}}{A function defining the secondary (global) equations in the model. Values are not estimated for these equations but they are available to every other block in the model. For example: @@ -236,8 +234,7 @@ Note that the function must be defined with no arguments between the parentheses, and the body \strong{must be in R syntax}. Any number of lines and R code, e.g. \code{if} - \verb{else} statements, etc. are permissible.} - -\item{\code{eqn}}{A function defining the model equations. The function must have no arguments. + \item{\code{eqn}}{A function defining the model equations. The function must have no arguments. The body of the function may contain three kinds of equations, written in R syntax. \itemize{ \item \strong{Implicit equations} referenced by calling the name of a Pmetrics model library object @@ -294,8 +291,7 @@ For example, the \code{sec} block, but will only be available within the \code{eqn} block as opposed to global availability when defined in \code{sec}. They can be added to either }} - -\item{\code{lag}}{A function defining the lag time (delayed absorption) for the bolus input. + \item{\code{lag}}{A function defining the lag time (delayed absorption) for the bolus input. The function must have no arguments, and the equations must be defined in R syntax The equations must be defined in the form of @@ -313,8 +309,7 @@ this code could be used to model delayed absorption if an antacid is present. As for \code{eqn}, additional equations in R code can be defined in this block, but will only be available within the \code{lag} block.} - -\item{\code{fa}}{A function defining the bioavailability (fraction absorbed) equations, + \item{\code{fa}}{A function defining the bioavailability (fraction absorbed) equations, similar to \code{lag}. Example: @@ -326,8 +321,7 @@ Example: As for \code{eqn}, additional equations in R code can be defined in this block, but will only be available within the \code{fa} block.} - -\item{\code{ini}}{A function defining the initial conditions for a compartment + \item{\code{ini}}{A function defining the initial conditions for a compartment in the model. Structure is similar to \code{lag} and \code{fa}. Example: @@ -344,8 +338,7 @@ of a covariate \code{init2} multiplied by the volume of the compartment, As for \code{eqn}, additional equations in R code can be defined in this block, but will only be available within the \code{ini} block.} - -\item{\code{out}}{A function defining the output equations, which are the predictions + \item{\code{out}}{A function defining the output equations, which are the predictions from the model. The function must have no arguments, and the equations for predictions must be defined in R syntax. @@ -405,8 +398,7 @@ equation since there is only one source of observations: plasma concentration. AUC (\code{y[2]}) is not fitted to any observations; it is a calculation based on the model state, given the optimized parameter values. It's not required, but shown here for illustrative purposes.} - -\item{\code{err}}{An unammed vector of error models for each of the output equations + \item{\code{err}}{An unammed vector of error models for each of the output equations with observations, i.e. those that have an \code{outeq} number associated with them in the data. Each error model is defined by the \code{\link[=proportional]{proportional()}} creator or @@ -435,66 +427,68 @@ for 3 outputs with the same error model: replicate(3, proportional(2, c(0.1, 0.15, 0, 0))) ) }\if{html}{\out{
}}} - -\item{\code{solver}}{Optional ODE solver for user-defined ODE models. + \item{\code{solver}}{Optional ODE solver for user-defined ODE models. Supported values are "BDF", "TRBDF2", "ESDIRK34", and "TSIT45". This is ignored for analytical library models.} - -\item{\code{...}}{Not currently used.} -} -\if{html}{\out{}} -} + \item{\code{...}}{Not currently used.} + } + \if{html}{\out{}} + } } + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_model-print}{}}} -\subsection{Method \code{print()}}{ -Print the model summary. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_model$print(...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Not used.} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -This method prints a summary of the model. +\subsection{\code{PM_model$print()}}{ + Print the model summary. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_model$print(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Not used.} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + This method prints a summary of the model. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_model-plot}{}}} -\subsection{Method \code{plot()}}{ -Plot the model. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_model$plot(...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Additional arguments passed to the plot function.} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -This method plots the model using the +\subsection{\code{PM_model$plot()}}{ + Plot the model. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_model$plot(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Additional arguments passed to the plot function.} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + This method plots the model using the \code{\link[=plot.PM_model]{plot.PM_model()}} function. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_model-fit}{}}} -\subsection{Method \code{fit()}}{ -This is the main method to run a population analysis. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_model$fit( +\subsection{\code{PM_model$fit()}}{ + This is the main method to run a population analysis. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_model$fit( data = NULL, path = ".", run = NULL, @@ -515,13 +509,13 @@ This is the main method to run a population analysis. algorithm = "NPAG", report = getPMoptions("report_template"), quiet = FALSE -)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{data}}{Either the name of a \link{PM_data} +)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{data}}{Either the name of a \link{PM_data} object in memory or the quoted filename (with or without a path) of a Pmetrics data file. If the path is not specified, the file is assumed to be in the current working directory, unless the \code{path} argument below is also specified as a global option for the fit. @@ -529,22 +523,16 @@ The file will be used to create a \link{PM_data} object on the fly. However, if created on the fly, this object will not be available to other methods or other instances of \verb{$fit()}.} - -\item{\code{path}}{Optional full path or relative path from current working directory + \item{\code{path}}{Optional full path or relative path from current working directory to the folder where \code{data} and \code{model} are located if specified as filenames without their own paths, and where the output will be saved. Default is the current working directory.} - -\item{\code{run}}{Specify the run number of the output folder. Default if missing is the next available number.} - -\item{\code{include}}{Vector of subject id values in the data file to include in the analysis. + \item{\code{run}}{Specify the run number of the output folder. Default if missing is the next available number.} + \item{\code{include}}{Vector of subject id values in the data file to include in the analysis. The default (missing) is all.} - -\item{\code{exclude}}{A vector of subject IDs to exclude in the analysis, e.g. \code{c(4,6:14,16:20)}} - -\item{\code{cycles}}{Number of cycles to run. Default is 100.} - -\item{\code{prior}}{The distribution for the initial support points, which can be + \item{\code{exclude}}{A vector of subject IDs to exclude in the analysis, e.g. \code{c(4,6:14,16:20)}} + \item{\code{cycles}}{Number of cycles to run. Default is 100.} + \item{\code{prior}}{The distribution for the initial support points, which can be one of several options. \itemize{ \item The default is "sobol", which is a semi-random distribution. This is the distribution @@ -573,8 +561,7 @@ as for the filename option above. Example: \code{fit1$run(prior = 2)}. is in the required format described in the filename option above. Example: \verb{mytheta <- read_csv("mytheta.csv"); fit1$run(prior = mytheta)}. }} - -\item{\code{points}}{The number of initial support points if one of + \item{\code{points}}{The number of initial support points if one of the semi-random, uniform distributions are selected in the \code{prior} argument above. Default is 100. The initial points are spread through the hyperspace defined by the random parameter ranges @@ -585,51 +572,38 @@ Pmetrics will suggest the minimum number of points that should be tried. The greater the initial number of points, the less chance of missing the globally maximally likely parameter value distribution, but the slower the run.} - -\item{\code{cache}}{Logical. Whether to enable runtime prediction caching during fitting. + \item{\code{cache}}{Logical. Whether to enable runtime prediction caching during fitting. Default is \code{TRUE}.} - -\item{\code{progress}}{Logical. Whether to print runtime likelihood-matrix progress during + \item{\code{progress}}{Logical. Whether to print runtime likelihood-matrix progress during fitting. Default is \code{TRUE}.} - -\item{\code{write_logs}}{Logical. Whether to write fit tracing logs to \code{outputs/log.txt}. + \item{\code{write_logs}}{Logical. Whether to write fit tracing logs to \code{outputs/log.txt}. Default is \code{FALSE}.} - -\item{\code{stdout_logs}}{Logical. Whether to print fit tracing logs in the R console. + \item{\code{stdout_logs}}{Logical. Whether to print fit tracing logs in the R console. Default is \code{TRUE}.} - -\item{\code{log_level}}{Character scalar. Fit tracing level. One of \code{"ERROR"}, \code{"WARN"}, + \item{\code{log_level}}{Character scalar. Fit tracing level. One of \code{"ERROR"}, \code{"WARN"}, \code{"INFO"}, \code{"DEBUG"}, or \code{"TRACE"}. Default is \code{"INFO"}.} - -\item{\code{idelta}}{How often to generate posterior predictions in units of time. + \item{\code{idelta}}{How often to generate posterior predictions in units of time. Default is 0.1, which means a prediction is generated every 0.1 hours (6 minutes) if the unit of time is hours. Predictions are made at this interval until the time of the last event (dose or observation) or until \code{tad} if that value is greater than the time of the last dose or observation in the data.} - -\item{\code{tad}}{Length of time after the last dose event to add additional predictions + \item{\code{tad}}{Length of time after the last dose event to add additional predictions at frequency \code{idelta}. Default is 0, which means no additional predictions beyond the last dose, assuming the dose is the last event. . If the last observation in the data is after \code{tad}, then a prediction will be generated at time = \code{tad} after the last dose} - -\item{\code{seed}}{Seed used if \code{prior = "sobol"}. Ignored otherwise.} - -\item{\code{overwrite}}{Boolean operator to overwrite existing run result folders. Default is \code{FALSE}.} - -\item{\code{algorithm}}{The algorithm to use for the run. Default is "NPAG" for the \strong{N}on-\strong{P}arametric \strong{A}daptive \strong{G}rid. Alternatives: "NPOD".} - -\item{\code{report}}{If missing, the default Pmetrics report template as specified in \link{getPMoptions} + \item{\code{seed}}{Seed used if \code{prior = "sobol"}. Ignored otherwise.} + \item{\code{overwrite}}{Boolean operator to overwrite existing run result folders. Default is \code{FALSE}.} + \item{\code{algorithm}}{The algorithm to use for the run. Default is "NPAG" for the \strong{N}on-\strong{P}arametric \strong{A}daptive \strong{G}rid. Alternatives: "NPOD".} + \item{\code{report}}{If missing, the default Pmetrics report template as specified in \link{getPMoptions} is used. Otherwise can be "plotly", "ggplot", or "none".} - -\item{\code{quiet}}{Boolean operator to suppress messages during the run. Default is \code{FALSE}.} - -\item{\code{intern}}{Run NPAG in the R console without a batch script. Default is TRUE.} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -As of Pmetrics 3.0.0, models contain compiled code to fit + \item{\code{quiet}}{Boolean operator to suppress messages during the run. Default is \code{FALSE}.} + \item{\code{intern}}{Run NPAG in the R console without a batch script. Default is TRUE.} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + As of Pmetrics 3.0.0, models contain compiled code to fit the model equations to the data, optimizing the parameter value probability distributions in the population to maximize their likelihood, or more precisely, minimize @@ -646,18 +620,18 @@ and the algorithm to use, among others. The \verb{$fit()} method is the descendant of the legacy \link{NPrun} function, which is maintained as a wrapper to \verb{$fit()} for backwards compatibility. -} - -\subsection{Returns}{ -A successful run will result in creation of a new folder in the working + } + \subsection{Returns}{ + A successful run will result in creation of a new folder in the working directory with the results inside the folder. + } } -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_model-map}{}}} -\subsection{Method \code{map()}}{ -Calculate posteriors from a fitted model. +\subsection{\code{PM_model$map()}}{ + Calculate posteriors from a fitted model. #' @details This method calculates posteriors from a compiled model. It is not necessary to have run the model first, but it is necessary to have an informative prior distribution. @@ -670,156 +644,164 @@ To calculate the posteriors, \code{map()} calls the \code{fit()} method with the and the \code{algorithm} argument set to "POSTPROB". If \code{data} are not provided as an argument to \code{map()}, the model's \code{data} field is used instead. If \code{data} is provided, it must be a \link{PM_data} object or the pathname of a file which can be loaded as a \link{PM_data} object. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_model$map(...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to the \code{fit} method. Note that the \code{cycles} argument is set to 0, + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_model$map(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to the \code{fit} method. Note that the \code{cycles} argument is set to 0, and the \code{algorithm} argument is set to "POSTPROB" automatically.} + } + \if{html}{\out{
}} + } } -\if{html}{\out{
}} -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_model-sim}{}}} -\subsection{Method \code{sim()}}{ -Simulate data from the model using a set of parameter values. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_model$sim(data, theta, quiet = FALSE)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{data}}{A \link{PM_data} object containing the dosing and observation information.} - -\item{\code{theta}}{A matrix of parameter values to use for the simulation. +\subsection{\code{PM_model$sim()}}{ + Simulate data from the model using a set of parameter values. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_model$sim(data, theta, quiet = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{data}}{A \link{PM_data} object containing the dosing and observation information.} + \item{\code{theta}}{A matrix of parameter values to use for the simulation. The \code{theta} matrix should have the same number of columns as the number of primary parameters in the model. Each row of \code{theta} represents a different set of parameter values.} - -\item{\code{quiet}}{Logical, if TRUE, suppresses messages during simulation.} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -This method simulates output from the model using a set of parameter values + \item{\code{quiet}}{Logical, if TRUE, suppresses messages during simulation.} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + This method simulates output from the model using a set of parameter values provided in the \code{theta} matrix and the template for dosing/observations in the \code{data} object. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_model-dsl}{}}} -\subsection{Method \code{dsl()}}{ -Return the lowered model DSL. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_model$dsl()}\if{html}{\out{
}} +\subsection{\code{PM_model$dsl()}}{ + Return the lowered model DSL. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_model$dsl()} + \if{html}{\out{
}} + } + \subsection{Details}{ + This method returns the pharmsol DSL source generated from the current model. + } } -\subsection{Details}{ -This method returns the pharmsol DSL source generated from the current model. -} - -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_model-debug_dsl}{}}} -\subsection{Method \code{debug_dsl()}}{ -Inspect the lowered model DSL and runtime validation result. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_model$debug_dsl(quiet = FALSE)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{quiet}}{Logical, if TRUE, suppresses messages and DSL printing.} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -This method is useful when you want to debug the exact DSL source that +\subsection{\code{PM_model$debug_dsl()}}{ + Inspect the lowered model DSL and runtime validation result. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_model$debug_dsl(quiet = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{quiet}}{Logical, if TRUE, suppresses messages and DSL printing.} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + This method is useful when you want to debug the exact DSL source that Pmetrics sends to the runtime. It returns the lowered DSL, a line-numbered copy, and either the rendered validation diagnostic or a success status. + } + \subsection{Returns}{ + A list with \code{ok}, \code{stage}, \code{dsl}, \code{numbered_dsl}, and \code{diagnostic}. + } } -\subsection{Returns}{ -A list with \code{ok}, \code{stage}, \code{dsl}, \code{numbered_dsl}, and \code{diagnostic}. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_model-compile}{}}} -\subsection{Method \code{compile()}}{ -Validate the model DSL. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_model$compile(quiet = FALSE)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{quiet}}{Logical, if TRUE, suppresses messages during compilation.} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -This method lowers the model to pharmsol DSL and validates that source +\subsection{\code{PM_model$compile()}}{ + Validate the model DSL. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_model$compile(quiet = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{quiet}}{Logical, if TRUE, suppresses messages during compilation.} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + This method lowers the model to pharmsol DSL and validates that source with the runtime compiler used for fitting and simulation. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_model-save}{}}} -\subsection{Method \code{save()}}{ -Save model to file (deprecated). -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_model$save()}\if{html}{\out{
}} -} - -\subsection{Details}{ -This method is deprecated. Existing or manually created model files may be read with \code{PM_model$new(filename)}, +\subsection{\code{PM_model$save()}}{ + Save model to file (deprecated). + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_model$save()} + \if{html}{\out{
}} + } + \subsection{Details}{ + This method is deprecated. Existing or manually created model files may be read with \code{PM_model$new(filename)}, but including model code in scripts is preferred, as this makes models used in runs transparent and more easily edited. Use the \code{PM_model$copy()} method instead to copy the model code to the clipboard and paste into scripts. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_model-copy}{}}} -\subsection{Method \code{copy()}}{ -Copy model code to clipboard. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_model$copy()}\if{html}{\out{
}} -} - -\subsection{Details}{ -This method copies the R code to create the model to the clipboard. +\subsection{\code{PM_model$copy()}}{ + Copy model code to clipboard. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_model$copy()} + \if{html}{\out{
}} + } + \subsection{Details}{ + This method copies the R code to create the model to the clipboard. This is useful for saving the model code in a script. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_model-clone}{}}} -\subsection{Method \code{clone()}}{ -The objects of this class are cloneable with this method. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_model$clone(deep = FALSE)}\if{html}{\out{
}} +\subsection{\code{PM_model$clone()}}{ + The objects of this class are cloneable with this method. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_model$clone(deep = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{deep}}{Whether to make a deep clone.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{deep}}{Whether to make a deep clone.} -} -\if{html}{\out{
}} -} -} } diff --git a/man/PM_op.Rd b/man/PM_op.Rd index 9e8ab0372..28554c0f9 100755 --- a/man/PM_op.Rd +++ b/man/PM_op.Rd @@ -33,159 +33,162 @@ allows you to access them in an S3 way, e.g. \code{run1$op$time} if \code{run1} Michael Neely, Julian Otalvaro } \section{Public fields}{ -\if{html}{\out{
}} -\describe{ -\item{\code{id}}{subject identification} + \if{html}{\out{
}} + \describe{ + \item{\code{id}}{subject identification} -\item{\code{time}}{observation time in relative units, usually hours} + \item{\code{time}}{observation time in relative units, usually hours} -\item{\code{obs}}{observation} + \item{\code{obs}}{observation} -\item{\code{cens}}{censoring information: "none" for observed, "bloq" for below limit of quantification, "aloq" for above limit of quantification} + \item{\code{cens}}{censoring information: "none" for observed, "bloq" for below limit of quantification, "aloq" for above limit of quantification} -\item{\code{pred}}{prediction} + \item{\code{pred}}{prediction} -\item{\code{pred.type}}{Population predictions based on Bayesian prior parameter value distribution, + \item{\code{pred.type}}{Population predictions based on Bayesian prior parameter value distribution, or individual predictions based on Bayesian posterior parameter value distributions} -\item{\code{icen}}{Predictions based on mean or median of Bayesian \code{pred.type}parameter values} + \item{\code{icen}}{Predictions based on mean or median of Bayesian \code{pred.type}parameter values} -\item{\code{outeq}}{output equation number} + \item{\code{outeq}}{output equation number} -\item{\code{block}}{dosing block number for each subject, as defined by dose resets (evid=4).} + \item{\code{block}}{dosing block number for each subject, as defined by dose resets (evid=4).} -\item{\code{obsSD}}{standard deviation of the observation based on the assay error polynomial} + \item{\code{obsSD}}{standard deviation of the observation based on the assay error polynomial} -\item{\code{d}}{prediction error, \code{pred} - \code{obs}} + \item{\code{d}}{prediction error, \code{pred} - \code{obs}} -\item{\code{ds}}{squared prediction error} + \item{\code{ds}}{squared prediction error} -\item{\code{wd}}{weighted prediction error, which is the prediction error divided by the \code{obsSD}} + \item{\code{wd}}{weighted prediction error, which is the prediction error divided by the \code{obsSD}} -\item{\code{wds}}{weighted squared prediction error} + \item{\code{wds}}{weighted squared prediction error} -\item{\code{data}}{A data frame of class \strong{PM_op_data} combining all the above fields as its columns} -} -\if{html}{\out{
}} + \item{\code{data}}{A data frame of class \strong{PM_op_data} combining all the above fields as its columns} + } + \if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ -\itemize{ -\item \href{#method-PM_op-new}{\code{PM_op$new()}} -\item \href{#method-PM_op-plot}{\code{PM_op$plot()}} -\item \href{#method-PM_op-summary}{\code{PM_op$summary()}} -\item \href{#method-PM_op-auc}{\code{PM_op$auc()}} -\item \href{#method-PM_op-clone}{\code{PM_op$clone()}} -} + \itemize{ + \item \href{#method-PM_op-initialize}{\code{PM_op$new()}} + \item \href{#method-PM_op-plot}{\code{PM_op$plot()}} + \item \href{#method-PM_op-summary}{\code{PM_op$summary()}} + \item \href{#method-PM_op-auc}{\code{PM_op$auc()}} + \item \href{#method-PM_op-clone}{\code{PM_op$clone()}} + } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_op-new}{}}} -\subsection{Method \code{new()}}{ -Create new object populated with observed vs. predicted data -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_op$new(PMdata = NULL, path = ".", ...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{PMdata}}{include Saved, parsed output of prior run, used when source files are not available. +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_op-initialize}{}}} +\subsection{\code{PM_op$new()}}{ + Create new object populated with observed vs. predicted data + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_op$new(PMdata = NULL, path = ".", ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{PMdata}}{include Saved, parsed output of prior run, used when source files are not available. .} - -\item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. + \item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. .} - -\item{\code{...}}{Not currently used.} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -Creation of new \code{PM_op} object is automatic at the end of a run and not generally necessary + \item{\code{...}}{Not currently used.} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + Creation of new \code{PM_op} object is automatic at the end of a run and not generally necessary for the user to do. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_op-plot}{}}} -\subsection{Method \code{plot()}}{ -Plot method -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_op$plot(...)}\if{html}{\out{
}} +\subsection{\code{PM_op$plot()}}{ + Plot method + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_op$plot(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{plot.PM_op}} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + See \link{plot.PM_op}. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{plot.PM_op}} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -See \link{plot.PM_op}. -} - -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_op-summary}{}}} -\subsection{Method \code{summary()}}{ -Summary method -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_op$summary(...)}\if{html}{\out{
}} +\subsection{\code{PM_op$summary()}}{ + Summary method + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_op$summary(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{summary.PM_op}} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + See \link{summary.PM_op}. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{summary.PM_op}} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -See \link{summary.PM_op}. -} - -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_op-auc}{}}} -\subsection{Method \code{auc()}}{ -Calculate AUC -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_op$auc(...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{make_AUC}} - -\item{\code{data}}{The object to use for AUC calculation} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -See \link{make_AUC} +\subsection{\code{PM_op$auc()}}{ + Calculate AUC + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_op$auc(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{make_AUC}} + \item{\code{data}}{The object to use for AUC calculation} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + See \link{make_AUC} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_op-clone}{}}} -\subsection{Method \code{clone()}}{ -The objects of this class are cloneable with this method. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_op$clone(deep = FALSE)}\if{html}{\out{
}} +\subsection{\code{PM_op$clone()}}{ + The objects of this class are cloneable with this method. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_op$clone(deep = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{deep}}{Whether to make a deep clone.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{deep}}{Whether to make a deep clone.} -} -\if{html}{\out{
}} -} -} } diff --git a/man/PM_opt.Rd b/man/PM_opt.Rd index 8a1c7ae93..d43727d80 100755 --- a/man/PM_opt.Rd +++ b/man/PM_opt.Rd @@ -29,42 +29,43 @@ Models Based on Minimizing Bayes Risk: Application to Voriconazole. Michael Neely } \section{Public fields}{ -\if{html}{\out{
}} -\describe{ -\item{\code{sampleTime}}{The optimal sample times, based on requested \code{type} + \if{html}{\out{
}} + \describe{ + \item{\code{sampleTime}}{The optimal sample times, based on requested \code{type} argument to \verb{$new} creation method.} -\item{\code{bayesRisk}}{Only present for MM-optimal sampling. + \item{\code{bayesRisk}}{Only present for MM-optimal sampling. The Bayesian risk of mis-classifying a subject based on the sample times. This is more useful for comparisons between sampling strategies, with minimization the goal.} -\item{\code{simdata}}{A \link{PM_sim} object with the simulated profiles} + \item{\code{simdata}}{A \link{PM_sim} object with the simulated profiles} -\item{\code{mmInt}}{A list with the \code{mmInt} values, \code{NULL} if \code{mmInt} argument + \item{\code{mmInt}}{A list with the \code{mmInt} values, \code{NULL} if \code{mmInt} argument to \verb{$new} is missing.} -} -\if{html}{\out{
}} + } + \if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ -\itemize{ -\item \href{#method-PM_opt-new}{\code{PM_opt$new()}} -\item \href{#method-PM_opt-plot}{\code{PM_opt$plot()}} -\item \href{#method-PM_opt-print}{\code{PM_opt$print()}} -\item \href{#method-PM_opt-clone}{\code{PM_opt$clone()}} -} + \itemize{ + \item \href{#method-PM_opt-initialize}{\code{PM_opt$new()}} + \item \href{#method-PM_opt-plot}{\code{PM_opt$plot()}} + \item \href{#method-PM_opt-print}{\code{PM_opt$print()}} + \item \href{#method-PM_opt-clone}{\code{PM_opt$clone()}} + } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_opt-new}{}}} -\subsection{Method \code{new()}}{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_opt-initialize}{}}} +\subsection{\code{PM_opt$new()}}{ + \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} Determine optimal sample times which are the most informative about the model parameters. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_opt$new( + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_opt$new( poppar, model, data, @@ -75,13 +76,13 @@ model parameters. algorithm = "mm", outeq = 1, ... -)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{poppar}}{There are several choices for the population parameters. +)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{poppar}}{There are several choices for the population parameters. \itemize{ \item A \link{PM_sim} object, in which case the simulated data will be used to calculate optimal sampling times. Here \code{model} and \code{data} are not required, as they will be extracted from the \link{PM_sim} object. @@ -104,8 +105,7 @@ number of columns equal to the number of parameters. Compatible \code{model} and \code{data} objects must be specified to simulate the output profiles from the support points generated by sampling from the specified distributions. }} - -\item{\code{model}}{A \link{PM_model} object, required or optional depending on the \code{poppar} argument. + \item{\code{model}}{A \link{PM_model} object, required or optional depending on the \code{poppar} argument. \itemize{ \item When \code{poppar} is a \link{PM_sim} object, \code{model} is ignored. \item When \code{poppar} is a \link{PM_result} object, \code{model} is optional. @@ -118,8 +118,7 @@ parameter names and values in the data frame. \item When \code{poppar} is a list of weights, means, and covariance, \code{model} is mandatory and must be compatible with the parameter names and values. }} - -\item{\code{data}}{A \link{PM_data} object, required or optional depending on the \code{poppar} argument. + \item{\code{data}}{A \link{PM_data} object, required or optional depending on the \code{poppar} argument. \itemize{ \item When \code{poppar} is a \link{PM_sim} object, \code{data} is ignored. \item When \code{poppar} is a \link{PM_result} object, \code{data} is optional. If missing, the data from the \verb{$data} field of the \link{PM_result} will be used. @@ -132,11 +131,9 @@ If specified, \code{data} must be compatible with the model that is either in th For any choice of \code{data}, the value for outputs can be coded as any number(s) other than -99. The number(s) will be replaced in the simulator output with the simulated values. Good practice is to use -1 for out values to be simulated.} - -\item{\code{nsamp}}{The number of MM-optimal sample times to compute; default + \item{\code{nsamp}}{The number of MM-optimal sample times to compute; default is 1, but can be any number. Values >4 will take an exponentially longer time.} - -\item{\code{weight}}{List whose names indicate the type of weighting, and + \item{\code{weight}}{List whose names indicate the type of weighting, and values indicate the relative weight. Values should sum to 1. Names can be any of the following: \itemize{ @@ -150,8 +147,7 @@ Any combination of AUC, max, and min can be chosen. If "none" is specified, oth weight types will be ignored and the relative value will be set to 1. For example,\code{list(auc = 0.5,max = 0.5)} or \code{list(auc = 0.2, min = 0.8)}. The default is \code{list(none = 1)}.} - -\item{\code{predInt}}{The interval in fractional hours for simulated predicted outputs at times other than those specified in the template \code{data}. + \item{\code{predInt}}{The interval in fractional hours for simulated predicted outputs at times other than those specified in the template \code{data}. The default is 0.5, which means there will be simulated outputs every 30 minutes from time 0 up to the maximal time in the template file. You may also specify \code{predInt} as a vector of 3 values, e.g. \code{c(1,4,1)}, similar to the R command \link{seq}, where the @@ -160,16 +156,12 @@ step value. Outputs for times specified in the template file will also be simul To simulate outputs \emph{only} at the output times in the template data (i.e. EVID=0 events), use \code{predInt = 0}. Note that the maximum number of predictions total is 594, so the interval must be sufficiently large to accommodate this for a given number of output equations and total time to simulate over. If \code{predInt} is set so that this cap is exceeded, predictions will be truncated.} - -\item{\code{mmInt}}{Specify the time intervals from which MMopt times can be selected. + \item{\code{mmInt}}{Specify the time intervals from which MMopt times can be selected. These should only include simulated times specified by \code{predInt}.} - -\item{\code{algorithm}}{Optimal sampling algorithm. Currently not modifiable and + \item{\code{algorithm}}{Optimal sampling algorithm. Currently not modifiable and the only option is "mm".} - -\item{\code{outeq}}{Output equation to optimize} - -\item{\code{...}}{Other parameters to pass to \link{PM_sim}$new(). Most are not necessary, + \item{\code{outeq}}{Output equation to optimize} + \item{\code{...}}{Other parameters to pass to \link{PM_sim}$new(). Most are not necessary, but \code{usePost = TRUE} can be used to calculate individual MMopt times. In this case, the number of posterior distributions contained in \code{poppar$final$postPoints} needs to match the number of subjects in \code{data}. @@ -181,67 +173,71 @@ Note that the following arguments to \link{PM_sim}$new cannot be modified. \item \code{outname} is "MMsim" \item \code{combine} is \code{TRUE} }} - -\item{\code{clean}}{Boolean parameter to specify whether temporary files made in the + \item{\code{clean}}{Boolean parameter to specify whether temporary files made in the course of the simulation run should be deleted. Defaults to \code{TRUE}. This is primarily used for debugging.} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -Currently, the only option is the multiple-model optimization algorithm. + } + \if{html}{\out{
}} + } + \subsection{Details}{ + Currently, the only option is the multiple-model optimization algorithm. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_opt-plot}{}}} -\subsection{Method \code{plot()}}{ -Plot method -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_opt$plot(...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{plot.PM_opt}} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -See \link{plot.PM_opt}. +\subsection{\code{PM_opt$plot()}}{ + Plot method + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_opt$plot(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{plot.PM_opt}} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + See \link{plot.PM_opt}. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_opt-print}{}}} -\subsection{Method \code{print()}}{ -Print method -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_opt$print()}\if{html}{\out{
}} +\subsection{\code{PM_opt$print()}}{ + Print method + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_opt$print()} + \if{html}{\out{
}} + } + \subsection{Returns}{ + Prints the optimal sampling times and Bayes Risk. + } } -\subsection{Returns}{ -Prints the optimal sampling times and Bayes Risk. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_opt-clone}{}}} -\subsection{Method \code{clone()}}{ -The objects of this class are cloneable with this method. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_opt$clone(deep = FALSE)}\if{html}{\out{
}} +\subsection{\code{PM_opt$clone()}}{ + The objects of this class are cloneable with this method. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_opt$clone(deep = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{deep}}{Whether to make a deep clone.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{deep}}{Whether to make a deep clone.} -} -\if{html}{\out{
}} -} -} } diff --git a/man/PM_parse.Rd b/man/PM_parse.Rd deleted file mode 100644 index d5922d719..000000000 --- a/man/PM_parse.Rd +++ /dev/null @@ -1,38 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/PM_parse.R -\name{PM_parse} -\alias{PM_parse} -\title{Parse Pmetrics output} -\usage{ -PM_parse(path = ".", fit = "fit.rds", write = TRUE) -} -\arguments{ -\item{path}{The directory containing the output from the Rust-implementation of NPAG} - -\item{fit}{The relative path to a "fit.rds" file, which is normal output after a fit containing the data and model used.} - -\item{write}{If \code{TRUE} (default), saves the output as "PMout.Rdata" in the specified path.} -} -\value{ -The output of \code{PM_parse} is a list containing the following elements -\itemize{ -\item \strong{op} Written to the standard of \code{\link[=PM_op]{PM_op()}} -\item \strong{pop} Written to the standard of \code{\link[=PM_pop]{PM_pop()}} -\item \strong{post} Written to the standard of \code{\link[=PM_post]{PM_post()}} -\item \strong{cycles} Written to the standard of \code{\link[=PM_cycle]{PM_cycle()}} -\item \strong{final} Written to the standard of \code{\link[=PM_final]{PM_final()}} -\item \strong{cov} Written to the standard of \code{\link[=PM_cov]{PM_cov()}} -} -} -\description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} - -A flexible parser for Pmetrics output -} -\details{ -Currently written for the Rust implementation of NPAG -} -\author{ -Michael Neely and Markus Hovd -} -\keyword{internal} diff --git a/man/PM_pop.Rd b/man/PM_pop.Rd index 135c9e0b2..58b8c6945 100755 --- a/man/PM_pop.Rd +++ b/man/PM_pop.Rd @@ -36,9 +36,9 @@ into the R console for documentation and examples. Michael Neely, Julian Otalvaro } \section{Public fields}{ -\if{html}{\out{
}} -\describe{ -\item{\code{data}}{A data frame with the following columns: + \if{html}{\out{
}} + \describe{ + \item{\code{data}}{A data frame with the following columns: \itemize{ \item \strong{id} Subject id \item \strong{time} Time of predictions in decimal hours @@ -47,128 +47,131 @@ Michael Neely, Julian Otalvaro \item \strong{pred} Predicted output for each outeq \item \strong{block} Observation blocks within subjects as defined by \emph{EVID=4} dosing events }} -} -\if{html}{\out{
}} + } + \if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ -\itemize{ -\item \href{#method-PM_pop-new}{\code{PM_pop$new()}} -\item \href{#method-PM_pop-plot}{\code{PM_pop$plot()}} -\item \href{#method-PM_pop-summary}{\code{PM_pop$summary()}} -\item \href{#method-PM_pop-auc}{\code{PM_pop$auc()}} -\item \href{#method-PM_pop-clone}{\code{PM_pop$clone()}} -} + \itemize{ + \item \href{#method-PM_pop-initialize}{\code{PM_pop$new()}} + \item \href{#method-PM_pop-plot}{\code{PM_pop$plot()}} + \item \href{#method-PM_pop-summary}{\code{PM_pop$summary()}} + \item \href{#method-PM_pop-auc}{\code{PM_pop$auc()}} + \item \href{#method-PM_pop-clone}{\code{PM_pop$clone()}} + } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_pop-new}{}}} -\subsection{Method \code{new()}}{ -Create new object populated with population predicted data at +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_pop-initialize}{}}} +\subsection{\code{PM_pop$new()}}{ + Create new object populated with population predicted data at regular, frequent intervals -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_pop$new(PMdata = NULL, path = ".", ...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{PMdata}}{include Saved, parsed output of prior run, used when source files are not available. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_pop$new(PMdata = NULL, path = ".", ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{PMdata}}{include Saved, parsed output of prior run, used when source files are not available. .} - -\item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. + \item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. .} - -\item{\code{...}}{Not currently used.} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -Creation of new \code{PM_pop} object is automatic and not generally necessary + \item{\code{...}}{Not currently used.} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + Creation of new \code{PM_pop} object is automatic and not generally necessary for the user to do. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_pop-plot}{}}} -\subsection{Method \code{plot()}}{ -Plot method -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_pop$plot(...)}\if{html}{\out{
}} +\subsection{\code{PM_pop$plot()}}{ + Plot method + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_pop$plot(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{plot.PM_pop}} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + See \link{plot.PM_pop}. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{plot.PM_pop}} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -See \link{plot.PM_pop}. -} - -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_pop-summary}{}}} -\subsection{Method \code{summary()}}{ -Summary method -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_pop$summary(...)}\if{html}{\out{
}} +\subsection{\code{PM_pop$summary()}}{ + Summary method + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_pop$summary(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{summary.PM_pop}} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + See \link{summary.PM_pop}. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{summary.PM_pop}} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -See \link{summary.PM_pop}. -} - -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_pop-auc}{}}} -\subsection{Method \code{auc()}}{ -Calculate AUC -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_pop$auc(...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{make_AUC}} - -\item{\code{data}}{The object to use for AUC calculation} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -See \link{make_AUC} +\subsection{\code{PM_pop$auc()}}{ + Calculate AUC + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_pop$auc(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{make_AUC}} + \item{\code{data}}{The object to use for AUC calculation} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + See \link{make_AUC} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_pop-clone}{}}} -\subsection{Method \code{clone()}}{ -The objects of this class are cloneable with this method. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_pop$clone(deep = FALSE)}\if{html}{\out{
}} +\subsection{\code{PM_pop$clone()}}{ + The objects of this class are cloneable with this method. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_pop$clone(deep = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{deep}}{Whether to make a deep clone.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{deep}}{Whether to make a deep clone.} -} -\if{html}{\out{
}} -} -} } diff --git a/man/PM_post.Rd b/man/PM_post.Rd index cfff660e4..b23e913db 100755 --- a/man/PM_post.Rd +++ b/man/PM_post.Rd @@ -36,9 +36,9 @@ into the R console for documentation and examples. Michael Neely, Julian Otalvaro } \section{Public fields}{ -\if{html}{\out{
}} -\describe{ -\item{\code{data}}{A data frame with the following columns: + \if{html}{\out{
}} + \describe{ + \item{\code{data}}{A data frame with the following columns: \itemize{ \item \strong{id} Subject id \item \strong{time} Time of predictions in decimal hours @@ -47,128 +47,131 @@ Michael Neely, Julian Otalvaro \item \strong{pred} Predicted output for each outeq \item \strong{block} Observation blocks within subjects as defined by \emph{EVID=4} dosing events }} -} -\if{html}{\out{
}} + } + \if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ -\itemize{ -\item \href{#method-PM_post-new}{\code{PM_post$new()}} -\item \href{#method-PM_post-plot}{\code{PM_post$plot()}} -\item \href{#method-PM_post-summary}{\code{PM_post$summary()}} -\item \href{#method-PM_post-auc}{\code{PM_post$auc()}} -\item \href{#method-PM_post-clone}{\code{PM_post$clone()}} -} + \itemize{ + \item \href{#method-PM_post-initialize}{\code{PM_post$new()}} + \item \href{#method-PM_post-plot}{\code{PM_post$plot()}} + \item \href{#method-PM_post-summary}{\code{PM_post$summary()}} + \item \href{#method-PM_post-auc}{\code{PM_post$auc()}} + \item \href{#method-PM_post-clone}{\code{PM_post$clone()}} + } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_post-new}{}}} -\subsection{Method \code{new()}}{ -Create new object populated with Bayesian posterior predicted data at +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_post-initialize}{}}} +\subsection{\code{PM_post$new()}}{ + Create new object populated with Bayesian posterior predicted data at regular, frequent intervals -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_post$new(PMdata = NULL, path = ".", ...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{PMdata}}{include Saved, parsed output of prior run, used when source files are not available. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_post$new(PMdata = NULL, path = ".", ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{PMdata}}{include Saved, parsed output of prior run, used when source files are not available. .} - -\item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. + \item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. .} - -\item{\code{...}}{Not currently used.} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -Creation of new \code{PM_post} object is automatic and not generally necessary + \item{\code{...}}{Not currently used.} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + Creation of new \code{PM_post} object is automatic and not generally necessary for the user to do. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_post-plot}{}}} -\subsection{Method \code{plot()}}{ -Plot method -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_post$plot(...)}\if{html}{\out{
}} +\subsection{\code{PM_post$plot()}}{ + Plot method + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_post$plot(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{plot.PM_pop}} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + See \link{plot.PM_pop}. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{plot.PM_pop}} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -See \link{plot.PM_pop}. -} - -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_post-summary}{}}} -\subsection{Method \code{summary()}}{ -Summary method -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_post$summary(...)}\if{html}{\out{
}} +\subsection{\code{PM_post$summary()}}{ + Summary method + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_post$summary(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{summary.PM_pop}} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + See \link{summary.PM_post}. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{summary.PM_pop}} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -See \link{summary.PM_post}. -} - -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_post-auc}{}}} -\subsection{Method \code{auc()}}{ -Calculate AUC -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_post$auc(...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{make_AUC}} - -\item{\code{data}}{The object to use for AUC calculation} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -See \link{make_AUC} +\subsection{\code{PM_post$auc()}}{ + Calculate AUC + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_post$auc(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{make_AUC}} + \item{\code{data}}{The object to use for AUC calculation} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + See \link{make_AUC} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_post-clone}{}}} -\subsection{Method \code{clone()}}{ -The objects of this class are cloneable with this method. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_post$clone(deep = FALSE)}\if{html}{\out{
}} +\subsection{\code{PM_post$clone()}}{ + The objects of this class are cloneable with this method. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_post$clone(deep = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{deep}}{Whether to make a deep clone.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{deep}}{Whether to make a deep clone.} -} -\if{html}{\out{
}} -} -} } diff --git a/man/PM_pri.Rd b/man/PM_pri.Rd index fccc90bce..4bc02366d 100755 --- a/man/PM_pri.Rd +++ b/man/PM_pri.Rd @@ -10,70 +10,75 @@ This is used internally by the \code{PM_model} class. } \keyword{internal} \section{Public fields}{ -\if{html}{\out{
}} -\describe{ -\item{\code{min}}{Minimum value of the range.} + \if{html}{\out{
}} + \describe{ + \item{\code{min}}{Minimum value of the range.} -\item{\code{max}}{Maximum value of the range.} + \item{\code{max}}{Maximum value of the range.} -\item{\code{mean}}{Mean value of the range, calculated as (min + max) / 2.} + \item{\code{mean}}{Mean value of the range, calculated as (min + max) / 2.} -\item{\code{sd}}{Standard deviation of the range, calculated as (max - min) / 6.} -} -\if{html}{\out{
}} + \item{\code{sd}}{Standard deviation of the range, calculated as (max - min) / 6.} + } + \if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ -\itemize{ -\item \href{#method-PM_pri-new}{\code{PM_pri$new()}} -\item \href{#method-PM_pri-print}{\code{PM_pri$print()}} -\item \href{#method-PM_pri-clone}{\code{PM_pri$clone()}} -} + \itemize{ + \item \href{#method-PM_pri-initialize}{\code{PM_pri$new()}} + \item \href{#method-PM_pri-print}{\code{PM_pri$print()}} + \item \href{#method-PM_pri-clone}{\code{PM_pri$clone()}} + } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_pri-new}{}}} -\subsection{Method \code{new()}}{ -Initialize a new range object. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_pri$new(min, max)}\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_pri-initialize}{}}} +\subsection{\code{PM_pri$new()}}{ + Initialize a new range object. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_pri$new(min, max)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{min}}{Minimum value of the range.} + \item{\code{max}}{Maximum value of the range.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{min}}{Minimum value of the range.} - -\item{\code{max}}{Maximum value of the range.} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_pri-print}{}}} -\subsection{Method \code{print()}}{ -Print the range. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_pri$print()}\if{html}{\out{
}} +\subsection{\code{PM_pri$print()}}{ + Print the range. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_pri$print()} + \if{html}{\out{
}} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_pri-clone}{}}} -\subsection{Method \code{clone()}}{ -The objects of this class are cloneable with this method. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_pri$clone(deep = FALSE)}\if{html}{\out{
}} +\subsection{\code{PM_pri$clone()}}{ + The objects of this class are cloneable with this method. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_pri$clone(deep = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{deep}}{Whether to make a deep clone.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{deep}}{Whether to make a deep clone.} -} -\if{html}{\out{
}} -} -} } diff --git a/man/PM_pta.Rd b/man/PM_pta.Rd index 9d6f7c51f..330261d8f 100755 --- a/man/PM_pta.Rd +++ b/man/PM_pta.Rd @@ -24,7 +24,7 @@ appropriate regimens. \examples{ ## ------------------------------------------------ -## Method `PM_pta$new` +## Method `PM_pta$new()` ## ------------------------------------------------ \dontrun{ @@ -73,30 +73,31 @@ Michael Neely and Jan Strojil } \keyword{internal} \section{Public fields}{ -\if{html}{\out{
}} -\describe{ -\item{\code{data}}{Contains the raw results.} -} -\if{html}{\out{
}} + \if{html}{\out{
}} + \describe{ + \item{\code{data}}{Contains the raw results.} + } + \if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ -\itemize{ -\item \href{#method-PM_pta-new}{\code{PM_pta$new()}} -\item \href{#method-PM_pta-save}{\code{PM_pta$save()}} -\item \href{#method-PM_pta-summary}{\code{PM_pta$summary()}} -\item \href{#method-PM_pta-plot}{\code{PM_pta$plot()}} -\item \href{#method-PM_pta-load}{\code{PM_pta$load()}} -\item \href{#method-PM_pta-clone}{\code{PM_pta$clone()}} -} + \itemize{ + \item \href{#method-PM_pta-initialize}{\code{PM_pta$new()}} + \item \href{#method-PM_pta-save}{\code{PM_pta$save()}} + \item \href{#method-PM_pta-summary}{\code{PM_pta$summary()}} + \item \href{#method-PM_pta-plot}{\code{PM_pta$plot()}} + \item \href{#method-PM_pta-load}{\code{PM_pta$load()}} + \item \href{#method-PM_pta-clone}{\code{PM_pta$clone()}} + } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_pta-new}{}}} -\subsection{Method \code{new()}}{ -Create a new \code{PM_pta} object. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_pta$new( +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_pta-initialize}{}}} +\subsection{\code{PM_pta$new()}}{ + Create a new \code{PM_pta} object. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_pta$new( simdata, simlabels, target, @@ -109,13 +110,13 @@ Create a new \code{PM_pta} object. icen = "median", block = 1, ... -)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{simdata}}{Can be one of multiple inputs as shown in the examples below using. +)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{simdata}}{Can be one of multiple inputs as shown in the examples below using. \itemize{ \item \code{simEx} \emph{PM_sim} \item \code{simEx$data} \emph{PM_simlist} @@ -126,10 +127,8 @@ Create a new \code{PM_pta} object. \item \code{NPex$data$standard_data} \emph{PM_data_data} \item \code{"simout.txt"} matches files with wildcard ability, see \link{PM_sim} }} - -\item{\code{simlabels}}{Optional character vector of labels for each simulation. Default is \code{c('Regimen 1', 'Regimen 2',...)}.} - -\item{\code{target}}{One of several options. + \item{\code{simlabels}}{Optional character vector of labels for each simulation. Default is \code{c('Regimen 1', 'Regimen 2',...)}.} + \item{\code{target}}{One of several options. \itemize{ \item A vector of pharmacodynamic targets, such as Minimum Inhibitory Concentrations (MICs), e.g. \code{c(0.25, 0.5, 1, 2, 4, 8, 16, 32)}. \item A single numerical value such as a concentration, e.g. 10. @@ -141,8 +140,7 @@ corresponding to a \code{target_type} of "max" as in this example: \code{target = list(c(0.25, 0.5, 1, 2, 4, 8, 16, 32), 10, 50)}. The first value can also be a sampled distribution made with \link{makePTAtarget}. }} - -\item{\code{target_type}}{A vector of the type for each \code{target}. For any, place a minus sign + \item{\code{target_type}}{A vector of the type for each \code{target}. For any, place a minus sign in front to make the success less than the target ratio, e.g. \code{target_type = c("min", "-min")}. Available types: \itemize{ @@ -160,42 +158,33 @@ Use "-min" to make the success less than the target ratio. This enables testing of a specific timed concentration (e.g. one hour after a dose or C1). Be sure that the time in the simulated data is used, e.g., 122 after a dose given at 120. As for the other target types, make the number negative to make the success less than the target ratio, eg -122.} - -\item{\code{success}}{A vector specifying the success statistics, e.g. 0.4 for proportion time (end-start) above target, and/or 100 for max:target. + \item{\code{success}}{A vector specifying the success statistics, e.g. 0.4 for proportion time (end-start) above target, and/or 100 for max:target. For example \code{success = 0.4} or \code{success = c(0.4, 100)}. The length must be the same as for \code{target} and \code{target_type}.} - -\item{\code{outeq}}{An integer specifying the number of the simulated output equation to use. Default is 1.} - -\item{\code{free_fraction}}{Proportion of free, active drug, expressed as a numeric value >=0 and <=1. Default is 1, i.e., + \item{\code{outeq}}{An integer specifying the number of the simulated output equation to use. Default is 1.} + \item{\code{free_fraction}}{Proportion of free, active drug, expressed as a numeric value >=0 and <=1. Default is 1, i.e., 100\% free drug or 0\% protein binding.} - -\item{\code{start}}{Specify the time to begin PTA calculations. Default is a vector with the first observation time for subjects + \item{\code{start}}{Specify the time to begin PTA calculations. Default is a vector with the first observation time for subjects in each element of \code{simdata}, e.g. dose regimen. If specified as a vector, values will be recycled as necessary.} - -\item{\code{end}}{Specify the time to end PTA calculations so that PTA is calculated + \item{\code{end}}{Specify the time to end PTA calculations so that PTA is calculated from \code{start} to \code{end}. Default for end is the maximum observation time for subjects in each element of \code{simdata}, e.g. dose regimen. If specified as a vector, values will be recycled as necessary. Subjects with insufficient data (fewer than 5 simulated observations) for a specified interval will trigger a warning. Ideally then, the simulated datset should contain sufficient observations within the interval specified by \code{start} and \code{end}.} - -\item{\code{icen}}{Can be either "median" for the predictions based on medians of \code{pred.type} parameter value + \item{\code{icen}}{Can be either "median" for the predictions based on medians of \code{pred.type} parameter value distributions, or "mean". Default is "median".} - -\item{\code{block}}{Which block to plot, where a new block is defined by dose resets (evid = 4); default is 1.} - -\item{\code{...}}{Not currently used} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -This function will calculate the probability of target attainment (PTA) + \item{\code{block}}{Which block to plot, where a new block is defined by dose resets (evid = 4); default is 1.} + \item{\code{...}}{Not currently used} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + This function will calculate the probability of target attainment (PTA) for any number of simulations, targets and definitions of success. Simulations typically differ by dose, but may differ by other features such as children vs. adults. -} - -\subsection{Returns}{ -A list of class \emph{PM_pta_data}, included in the \code{data} field of the \link{PM_pta} object. + } + \subsection{Returns}{ + A list of class \emph{PM_pta_data}, included in the \code{data} field of the \link{PM_pta} object. The list contains each \code{target_type} as an element, followed by a final \code{intersection} element showing the results for profiles which meet ALL the conditions (intersection) or \code{NA} if only one \code{target_type} was specified. @@ -220,11 +209,10 @@ Simulated (rather than discrete) targets made with \link{makePTAtarget} will be abbreviated as "(sim)", e.g. (sim)(5) for a combination of simulated targets and a single concentration target of 5. } -} -\subsection{Examples}{ -\if{html}{\out{
}} -\preformatted{\dontrun{ -pta1 <- PM_pta$new(simEx, + } + \subsection{Examples}{ + \if{html}{\out{
}} + \preformatted{pta1 <- PM_pta$new(simEx, simlabels = c("600 mg daily", "1200 mg daily", "300 mg bid", "600 mg bid"), target = list(2^(-2:6), 1, 50), target_type = c("time", 144, "-max"), @@ -258,98 +246,106 @@ pta5 <- PM_pta$new(simdata = simEx, success = c(200,1), start = 120, end = 144) } + \if{html}{\out{
}} + } } -\if{html}{\out{
}} -} - -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_pta-save}{}}} -\subsection{Method \code{save()}}{ -Save the current PM_pta object into a .rds file. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_pta$save(file_name = "PMpta.rds")}\if{html}{\out{
}} +\subsection{\code{PM_pta$save()}}{ + Save the current PM_pta object into a .rds file. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_pta$save(file_name = "PMpta.rds")} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{file_name}}{Name of the file to be created, the default is PMpta.rds} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{file_name}}{Name of the file to be created, the default is PMpta.rds} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_pta-summary}{}}} -\subsection{Method \code{summary()}}{ -Summarize the \code{PM_pta} object. See \link{summary.PM_pta}. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_pta$summary(...)}\if{html}{\out{
}} +\subsection{\code{PM_pta$summary()}}{ + Summarize the \code{PM_pta} object. See \link{summary.PM_pta}. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_pta$summary(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{summary.PM_pta}} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{summary.PM_pta}} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_pta-plot}{}}} -\subsection{Method \code{plot()}}{ -Plot the \code{PM_pta} object. See \link{plot.PM_pta}. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_pta$plot(...)}\if{html}{\out{
}} +\subsection{\code{PM_pta$plot()}}{ + Plot the \code{PM_pta} object. See \link{plot.PM_pta}. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_pta$plot(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{plot.PM_pta}} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{plot.PM_pta}} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_pta-load}{}}} -\subsection{Method \code{load()}}{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +\subsection{\code{PM_pta$load()}}{ + \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Deprecated method to load a prior PTA Replaced by \code{PM_pta$new()} to be consistent with R6. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_pta$load(...)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_pta$load(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Not used.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Not used.} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_pta-clone}{}}} -\subsection{Method \code{clone()}}{ -The objects of this class are cloneable with this method. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_pta$clone(deep = FALSE)}\if{html}{\out{
}} +\subsection{\code{PM_pta$clone()}}{ + The objects of this class are cloneable with this method. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_pta$clone(deep = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{deep}}{Whether to make a deep clone.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{deep}}{Whether to make a deep clone.} -} -\if{html}{\out{
}} -} -} } diff --git a/man/PM_result.Rd b/man/PM_result.Rd index e1fb85b72..e8390c008 100755 --- a/man/PM_result.Rd +++ b/man/PM_result.Rd @@ -19,260 +19,269 @@ Michael Neely, Julian Otalvaro } \keyword{internal} \section{Public fields}{ -\if{html}{\out{
}} -\describe{ -\item{\code{pop}}{A \link{PM_pop} object} + \if{html}{\out{
}} + \describe{ + \item{\code{pop}}{A \link{PM_pop} object} -\item{\code{post}}{A \link{PM_post} object} + \item{\code{post}}{A \link{PM_post} object} -\item{\code{final}}{A \link{PM_final} object} + \item{\code{final}}{A \link{PM_final} object} -\item{\code{cycle}}{A \link{PM_cycle} object} + \item{\code{cycle}}{A \link{PM_cycle} object} -\item{\code{op}}{A \link{PM_op} object} + \item{\code{op}}{A \link{PM_op} object} -\item{\code{cov}}{A \link{PM_cov} object} + \item{\code{cov}}{A \link{PM_cov} object} -\item{\code{data}}{\link{PM_data} object representing the original .csv data file used in the run. The predictions contained in the \verb{$data} fields from \verb{$pop} and \verb{$post} will be added to this \link{PM_data} object to permit easy addition of such predictions to raw data plots. See \link{plot.PM_data} for more details.} + \item{\code{data}}{\link{PM_data} object representing the original .csv data file used in the run. The predictions contained in the \verb{$data} fields from \verb{$pop} and \verb{$post} will be added to this \link{PM_data} object to permit easy addition of such predictions to raw data plots. See \link{plot.PM_data} for more details.} -\item{\code{model}}{text string representing the original model file used in the run} + \item{\code{model}}{text string representing the original model file used in the run} -\item{\code{errfile}}{Name of error file if it exists} + \item{\code{errfile}}{Name of error file if it exists} -\item{\code{success}}{Boolean if successful run} + \item{\code{success}}{Boolean if successful run} -\item{\code{valid}}{If the \verb{$validate} method has been executed after a run, + \item{\code{valid}}{If the \verb{$validate} method has been executed after a run, this field will contain the information required to plot visual predictive checks and normalized prediction error discrepancies via the npde code developed by Comets et al. Use the \verb{$save} method on the augmented \code{PM_result} object to save it with the new validation results.} -\item{\code{opt_samp}}{If the \verb{$opt} method has been executed after a run, this + \item{\code{opt_samp}}{If the \verb{$opt} method has been executed after a run, this field will contain a \link{PM_opt} object which has optimal sampling times and methods to plot them. Use the \verb{$save} method on the augmented \code{PM_result} object to save it with the new optimal sampling results.} -} -\if{html}{\out{
}} + } + \if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ -\itemize{ -\item \href{#method-PM_result-new}{\code{PM_result$new()}} -\item \href{#method-PM_result-fit}{\code{PM_result$fit()}} -\item \href{#method-PM_result-plot}{\code{PM_result$plot()}} -\item \href{#method-PM_result-summary}{\code{PM_result$summary()}} -\item \href{#method-PM_result-auc}{\code{PM_result$auc()}} -\item \href{#method-PM_result-nca}{\code{PM_result$nca()}} -\item \href{#method-PM_result-report}{\code{PM_result$report()}} -\item \href{#method-PM_result-sim}{\code{PM_result$sim()}} -\item \href{#method-PM_result-save}{\code{PM_result$save()}} -\item \href{#method-PM_result-validate}{\code{PM_result$validate()}} -\item \href{#method-PM_result-step}{\code{PM_result$step()}} -\item \href{#method-PM_result-opt}{\code{PM_result$opt()}} -\item \href{#method-PM_result-load}{\code{PM_result$load()}} -\item \href{#method-PM_result-continue}{\code{PM_result$continue()}} -\item \href{#method-PM_result-clone}{\code{PM_result$clone()}} -} + \itemize{ + \item \href{#method-PM_result-initialize}{\code{PM_result$new()}} + \item \href{#method-PM_result-fit}{\code{PM_result$fit()}} + \item \href{#method-PM_result-plot}{\code{PM_result$plot()}} + \item \href{#method-PM_result-summary}{\code{PM_result$summary()}} + \item \href{#method-PM_result-auc}{\code{PM_result$auc()}} + \item \href{#method-PM_result-nca}{\code{PM_result$nca()}} + \item \href{#method-PM_result-report}{\code{PM_result$report()}} + \item \href{#method-PM_result-sim}{\code{PM_result$sim()}} + \item \href{#method-PM_result-save}{\code{PM_result$save()}} + \item \href{#method-PM_result-validate}{\code{PM_result$validate()}} + \item \href{#method-PM_result-step}{\code{PM_result$step()}} + \item \href{#method-PM_result-opt}{\code{PM_result$opt()}} + \item \href{#method-PM_result-load}{\code{PM_result$load()}} + \item \href{#method-PM_result-continue}{\code{PM_result$continue()}} + \item \href{#method-PM_result-export}{\code{PM_result$export()}} + \item \href{#method-PM_result-clone}{\code{PM_result$clone()}} + } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_result-new}{}}} -\subsection{Method \code{new()}}{ -Create new object populated with data from previous run -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_result$new(out, path = ".", quiet = TRUE)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{out}}{The parsed output from \link{PM_load}, which is +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_result-initialize}{}}} +\subsection{\code{PM_result$new()}}{ + Create new object populated with data from previous run + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_result$new(out, path = ".", quiet = TRUE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{out}}{The parsed output from \link{PM_load}, which is automatically generated. This is not a user-modifiable.} - -\item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. + \item{\code{path}}{include Path to the folder containing the raw results of the run. Default is the current working directory. .} - -\item{\code{quiet}}{Quietly validate. Default is \code{FALSE}.} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -Creation of new \code{PM_result} objects is via \link{PM_load}. + \item{\code{quiet}}{Quietly validate. Default is \code{FALSE}.} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + Creation of new \code{PM_result} objects is via \link{PM_load}. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-fit}{}}} -\subsection{Method \code{fit()}}{ -Fit the model to the data +\subsection{\code{PM_result$fit()}}{ + Fit the model to the data #' @details This method is used to fit the model in the \link{PM_result} object to data. It calls the \verb{$fit} method of the model stored in the \code{model} field. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_result$fit(data, ...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{data}}{Optional data to fit. If not provided, the data stored in the + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_result$fit(data, ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{data}}{Optional data to fit. If not provided, the data stored in the \code{data} field of the \link{PM_result} object will be used. This can be useful to continue a prior run that did not converge before the maximum number of cycles, e.g. \code{run2 <- run1$fit(cycles = 10000, prior = 1)}} - -\item{\code{...}}{Additional arguments passed to the model's \verb{$fit} method.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -Returns an invisible \link{PM_result}. -} + \item{\code{...}}{Additional arguments passed to the model's \verb{$fit} method.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + Returns an invisible \link{PM_result}. + } } + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-plot}{}}} -\subsection{Method \code{plot()}}{ -Plot generic function based on type -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_result$plot(type, ...)}\if{html}{\out{
}} +\subsection{\code{PM_result$plot()}}{ + Plot generic function based on type + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_result$plot(type, ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{type}}{Type of plot based on class of object} + \item{\code{...}}{Plot-specific arguments} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{type}}{Type of plot based on class of object} - -\item{\code{...}}{Plot-specific arguments} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-summary}{}}} -\subsection{Method \code{summary()}}{ -Summary generic function based on type -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_result$summary(type, ...)}\if{html}{\out{
}} +\subsection{\code{PM_result$summary()}}{ + Summary generic function based on type + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_result$summary(type, ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{type}}{Type of summary based on class of object} + \item{\code{...}}{Summary-specific arguments} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{type}}{Type of summary based on class of object} - -\item{\code{...}}{Summary-specific arguments} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-auc}{}}} -\subsection{Method \code{auc()}}{ -AUC generic function based on type -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_result$auc(src, ...)}\if{html}{\out{
}} +\subsection{\code{PM_result$auc()}}{ + AUC generic function based on type + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_result$auc(src, ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{src}}{Source of AUC, one of "op", "pop", "post", or "sim"} + \item{\code{...}}{Summary-specific arguments} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{src}}{Source of AUC, one of "op", "pop", "post", or "sim"} - -\item{\code{...}}{Summary-specific arguments} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-nca}{}}} -\subsection{Method \code{nca()}}{ -Perform non-compartmental analysis -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_result$nca(...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{make_NCA}.} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -See \link{make_NCA}. +\subsection{\code{PM_result$nca()}}{ + Perform non-compartmental analysis + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_result$nca(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{make_NCA}.} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + See \link{make_NCA}. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-report}{}}} -\subsection{Method \code{report()}}{ -Re-generate the report -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_result$report(...)}\if{html}{\out{
}} +\subsection{\code{PM_result$report()}}{ + Re-generate the report + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_result$report(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Parameters passed to \link{PM_report}. In particular, pay attention to \code{path}.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Parameters passed to \link{PM_report}. In particular, pay attention to \code{path}.} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-sim}{}}} -\subsection{Method \code{sim()}}{ -Calls \link{PM_sim}. Default is to use the \verb{$final}, \verb{$model}, and \verb{$data} objects +\subsection{\code{PM_result$sim()}}{ + Calls \link{PM_sim}. Default is to use the \verb{$final}, \verb{$model}, and \verb{$data} objects within the \link{PM_result}. It is common to supply a different \code{data} template. Occasionally it is necessary to use a different \code{model} with the \verb{$final} field, or vice versa. If all three are different, use \code{PM_sim$new()} instead. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_result$sim(...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Parameters passed to \link{PM_sim}. If using the \verb{$final}, \verb{$model}, and + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_result$sim(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Parameters passed to \link{PM_sim}. If using the \verb{$final}, \verb{$model}, and \verb{$data} fields, it is not necessary to specify these. Alternates for any of these should be specified. Other parameters for \link{PM_sim} should be passed as named arguments, e.g. \verb{$sim(include = 1:2, predInt = 1, limits = NA)}.} + } + \if{html}{\out{
}} + } } -\if{html}{\out{
}} -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-save}{}}} -\subsection{Method \code{save()}}{ -Save the current PM_result object to an .Rdata file. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_result$save(run, file = "PMout.Rdata")}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{run}}{The run output folder number to save the revised result. If missing, +\subsection{\code{PM_result$save()}}{ + Save the current PM_result object to an .Rdata file. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_result$save(run, file = "PMout.Rdata")} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{run}}{The run output folder number to save the revised result. If missing, will save in the current working directory. For example, if folder "1" is in your current working directory, specify \code{run = 1} to save the result to the "outputs" subfolder of the "1" folder.} - -\item{\code{file}}{Custom file name. Default is "PMout.Rdata". If \code{run} is not specified, \code{file} + \item{\code{file}}{Custom file name. Default is "PMout.Rdata". If \code{run} is not specified, \code{file} should be the full path and filename.} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -This is useful if you have updated the result in some way, for example you + } + \if{html}{\out{
}} + } + \subsection{Details}{ + This is useful if you have updated the result in some way, for example you have run the \verb{$validate()} method on the \code{PM_result} object, which returns an internal simulation based validation as a new \code{valid} field. To save this validation, use this \verb{$save} method. Note that unless a \code{file} name is provided, @@ -284,126 +293,147 @@ results with someone. The saved object is an .Rdata file. When loaded, it should be assigned to an R object, e.g. \code{run2 <- PM_result$new("filename")}. An equivalent statement would be \code{run2 <- PM_load(file = "filename")}. + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-validate}{}}} -\subsection{Method \code{validate()}}{ -Validate the result by internal simulation methods. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_result$validate(...)}\if{html}{\out{
}} +\subsection{\code{PM_result$validate()}}{ + Validate the result by internal simulation methods. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_result$validate(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{PM_valid}.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{PM_valid}.} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-step}{}}} -\subsection{Method \code{step()}}{ -Conduct stepwise linear regression of Bayesian posterior parameter values +\subsection{\code{PM_result$step()}}{ + Conduct stepwise linear regression of Bayesian posterior parameter values and covariates. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_result$step(...)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_result$step(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{PM_step}.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{PM_step}.} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-opt}{}}} -\subsection{Method \code{opt()}}{ -Calculate optimal sampling times. +\subsection{\code{PM_result$opt()}}{ + Calculate optimal sampling times. Method to compute optimal sampling times. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_result$opt(...)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_result$opt(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Parameters to pass to \link{PM_opt}.} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + See \link{PM_opt} for details. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Parameters to pass to \link{PM_opt}.} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -See \link{PM_opt} for details. -} - -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-load}{}}} -\subsection{Method \code{load()}}{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +\subsection{\code{PM_result$load()}}{ + \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Deprecated method to load prior results saved with the \verb{$save} method. Replaced by \link{PM_load}. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_result$load(...)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_result$load(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Not used.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Not used.} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-continue}{}}} -\subsection{Method \code{continue()}}{ -Continue fitting the model to the data using the final population points +\subsection{\code{PM_result$continue()}}{ + Continue fitting the model to the data using the final population points from the prior run as the prior for the new run. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_result$continue(...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Additional arguments passed to the underlying \code{fit} method of the model. See the documentation for \code{fit} for details on supported parameters.} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -This method is useful if a prior run did not converge before the maximum + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_result$continue(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Additional arguments passed to the underlying \code{fit} method of the model. See the documentation for \code{fit} for details on supported parameters.} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + This method is useful if a prior run did not converge before the maximum number of cycles. It uses the final population points from the prior run as the prior for a new run. The data and model are the same as in the prior run. + } } +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_result-export}{}}} +\subsection{\code{PM_result$export()}}{ + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_result$export(path = ".")} + \if{html}{\out{
}} + } } + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-clone}{}}} -\subsection{Method \code{clone()}}{ -The objects of this class are cloneable with this method. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_result$clone(deep = FALSE)}\if{html}{\out{
}} +\subsection{\code{PM_result$clone()}}{ + The objects of this class are cloneable with this method. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_result$clone(deep = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{deep}}{Whether to make a deep clone.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{deep}}{Whether to make a deep clone.} -} -\if{html}{\out{
}} -} -} } diff --git a/man/PM_sim.Rd b/man/PM_sim.Rd index 25c2bb518..9c557eb0c 100755 --- a/man/PM_sim.Rd +++ b/man/PM_sim.Rd @@ -21,7 +21,7 @@ can be found in the documentation for \verb{$new} method below. \examples{ ## ------------------------------------------------ -## Method `PM_sim$new` +## Method `PM_sim$new()` ## ------------------------------------------------ \dontrun{ @@ -79,9 +79,9 @@ Michael Neely } \keyword{internal} \section{Public fields}{ -\if{html}{\out{
}} -\describe{ -\item{\code{data}}{A list of class \emph{PM_sim} that contains the following elements. + \if{html}{\out{
}} + \describe{ + \item{\code{data}}{A list of class \emph{PM_sim} that contains the following elements. \itemize{ \item \strong{obs} Observations for all simulated templates as a data frame with these columns: \itemize{ @@ -126,50 +126,51 @@ this is a data frame of such matrices, one for each template in the \code{data} and an \code{id} column to identify the template in the \code{data} source. Again, this can be useful as a check against the original covariance in \code{poppar}. }} -} -\if{html}{\out{
}} + } + \if{html}{\out{
}} } \section{Active bindings}{ -\if{html}{\out{
}} -\describe{ -\item{\code{obs}}{Same as \code{obs} element in the \code{data} field.} + \if{html}{\out{
}} + \describe{ + \item{\code{obs}}{Same as \code{obs} element in the \code{data} field.} -\item{\code{amt}}{Same as \code{amt} element in the \code{data} field.} + \item{\code{amt}}{Same as \code{amt} element in the \code{data} field.} -\item{\code{parValues}}{Same as \code{parValues} element in the \code{data} field.} + \item{\code{parValues}}{Same as \code{parValues} element in the \code{data} field.} -\item{\code{totalSets}}{Same as \code{totalSets} element in the \code{data} field.} + \item{\code{totalSets}}{Same as \code{totalSets} element in the \code{data} field.} -\item{\code{totalMeans}}{Same as \code{totalMeans} element in the \code{data} field.} + \item{\code{totalMeans}}{Same as \code{totalMeans} element in the \code{data} field.} -\item{\code{totalCov}}{Same as \code{totalCov} element in the \code{data} field.} -} -\if{html}{\out{
}} + \item{\code{totalCov}}{Same as \code{totalCov} element in the \code{data} field.} + } + \if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ -\itemize{ -\item \href{#method-PM_sim-new}{\code{PM_sim$new()}} -\item \href{#method-PM_sim-save}{\code{PM_sim$save()}} -\item \href{#method-PM_sim-plot}{\code{PM_sim$plot()}} -\item \href{#method-PM_sim-opt}{\code{PM_sim$opt()}} -\item \href{#method-PM_sim-pta}{\code{PM_sim$pta()}} -\item \href{#method-PM_sim-auc}{\code{PM_sim$auc()}} -\item \href{#method-PM_sim-summary}{\code{PM_sim$summary()}} -\item \href{#method-PM_sim-run}{\code{PM_sim$run()}} -\item \href{#method-PM_sim-load}{\code{PM_sim$load()}} -\item \href{#method-PM_sim-clone}{\code{PM_sim$clone()}} -} + \itemize{ + \item \href{#method-PM_sim-initialize}{\code{PM_sim$new()}} + \item \href{#method-PM_sim-save}{\code{PM_sim$save()}} + \item \href{#method-PM_sim-plot}{\code{PM_sim$plot()}} + \item \href{#method-PM_sim-opt}{\code{PM_sim$opt()}} + \item \href{#method-PM_sim-pta}{\code{PM_sim$pta()}} + \item \href{#method-PM_sim-auc}{\code{PM_sim$auc()}} + \item \href{#method-PM_sim-summary}{\code{PM_sim$summary()}} + \item \href{#method-PM_sim-run}{\code{PM_sim$run()}} + \item \href{#method-PM_sim-load}{\code{PM_sim$load()}} + \item \href{#method-PM_sim-clone}{\code{PM_sim$clone()}} + } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_sim-new}{}}} -\subsection{Method \code{new()}}{ -This function simulates outputs from given inputs and a model. +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_sim-initialize}{}}} +\subsection{\code{PM_sim$new()}}{ + This function simulates outputs from given inputs and a model. It can be called directly or via the \verb{$sim} method for \link{PM_result} objects. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_sim$new( + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_sim$new( poppar, model, data, @@ -186,13 +187,13 @@ or via the \verb{$sim} method for \link{PM_result} objects. makecsv = NULL, quiet = FALSE, ... -)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{poppar}}{One of five things: +)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{poppar}}{One of five things: \enumerate{ \item A \link{PM_result} object containing the final population parameter distribution from a model run, a model object, and a data object. @@ -283,15 +284,13 @@ treated as the parameters to be used for one simulation. sim1 <- PM_sim$new(poppar = poppar, model = run1$model, data = run1$data) }\if{html}{\out{
}} }} - -\item{\code{model}}{Name of a suitable \link{PM_model} object or a model file template + \item{\code{model}}{Name of a suitable \link{PM_model} object or a model file template in the working directory. If missing, and \code{poppar} is a \link{PM_result}, the model within the \verb{$model} field of the \link{PM_result} object will be used. If \code{model} is missing and \code{poppar} is not a \link{PM_result}, then Pmetrics will attempt to load a model file in the working directory called "model.txt" as the default name.} - -\item{\code{data}}{Either a \link{PM_data} object or a character vector + \item{\code{data}}{Either a \link{PM_data} object or a character vector with the file name of a Pmetrics data file in the working directory that contains \strong{template} regimens and observation times. The value for outputs can be coded as any number(s) other than -99. The @@ -302,8 +301,7 @@ the data within the \verb{$data} field of the \link{PM_result} object will be us If \code{data} is missing and \code{poppar} is not a \link{PM_result}, then Pmetrics will attempt to load a data template file in the working directory called "data.csv" as the default name.} - -\item{\code{limits}}{If limits are specified, each simulated parameter set that + \item{\code{limits}}{If limits are specified, each simulated parameter set that contains a value outside of the limits will be ignored and another set will be generated. Four options exist for limits. \itemize{ @@ -335,30 +333,25 @@ verify the simulation, but only those sets within the specified limits will be used to generate output(s) and the means and covariances of the retained sets may (and likely will be) different than those specified by \code{poppar}.} - -\item{\code{split}}{Boolean operator controlling whether to split an NPAG + \item{\code{split}}{Boolean operator controlling whether to split an NPAG \link{PM_final} object into one distribution per support point, with means equal to the vector of parameter values for that point, and covariance equal to the population covariance divided by the number of support points. Default for NPAG \link{PM_final} objects is \code{TRUE}, otherwise \code{FALSE}.} - -\item{\code{include}}{A vector of subject IDs in the \code{data} to iterate + \item{\code{include}}{A vector of subject IDs in the \code{data} to iterate through, with each subject serving as the source of an independent simulation. Default is \code{NA} and all subjects in the data file will be used.} - -\item{\code{exclude}}{A vector of subject IDs to exclude in the simulation, e.g. + \item{\code{exclude}}{A vector of subject IDs to exclude in the simulation, e.g. \code{exclude = c(4, 6:14, 16:20)}. Default is \code{NA} and all subjects in the data file will be used, i.e. none excluded. Using both \code{include} and \code{exclude} criteria may result in conflicts.} - -\item{\code{nsim}}{The number of simulated profiles to create, per subject. Default + \item{\code{nsim}}{The number of simulated profiles to create, per subject. Default is 1000. Entering 0 will result in one profile being simulated from each point in the non-parametric prior (for NPAG final objects only). If \code{poppar} is supplied as a data frame of parameter values, \code{nsim} is ignored and the number of simulated profiles will equal the number of rows in that data frame.} - -\item{\code{predInt}}{The interval in fractional hours for simulated predicted + \item{\code{predInt}}{The interval in fractional hours for simulated predicted outputs at times other than those specified in the template \code{data}. The default is 0, which means there will be simulated outputs only at times specified in the data file (see below). Values greater than 0 result in @@ -377,8 +370,7 @@ predictions is 594, so the prediction interval must be sufficiently long to accommodate this for a given number of output equations and total time to simulate over. If \code{predInt} is set so that this cap is exceeded, predictions will be truncated.} - -\item{\code{covariate}}{Pmetrics can simulate values for some/all covariates + \item{\code{covariate}}{Pmetrics can simulate values for some/all covariates declared in the \code{cov} block of the \link{PM_model}. This argument is a list with the following named elements. \itemize{ @@ -500,8 +492,7 @@ Cases 1 or 2 when age was a covariate in the data and model. A character vector (not a list) of model covariates to fix and not simulate. Values in the template data will be used and not simulated. Example: \code{covariate = list(..., fix = c("wt", "age"))}.} - -\item{\code{usePost}}{Boolean, default \code{FALSE}. Only applicable when \code{poppar} contains an + \item{\code{usePost}}{Boolean, default \code{FALSE}. Only applicable when \code{poppar} contains an NPAG \link{PM_final} object. If \code{TRUE}, the mean posterior parameter values and covariances for each subject, modified by \code{include} or \code{exclude}, @@ -511,12 +502,10 @@ Normally one uses the same data file as used to make the model final parameter distribution in \code{poppar}, but if different templates are desired, the number must be equivalent to the number of included subjects from whom the posteriors are obtained.} - -\item{\code{seed}}{The seed for the random number generator. For \code{nsub} > 1, + \item{\code{seed}}{The seed for the random number generator. For \code{nsub} > 1, should be a vector of length equal to \code{nsub}. Shorter vectors will be recycled as necessary. Default is -17.} - -\item{\code{noise}}{A named list to add noise to most template data fields, + \item{\code{noise}}{A named list to add noise to most template data fields, including covariates. The default is \code{NULL}, which means no noise will be added. The name of each element in the list should correspond to @@ -561,8 +550,7 @@ Example: out = list(c(0.1, 0.001), mode = "exp"))) }\if{html}{\out{
}} }} - -\item{\code{makecsv}}{A character vector for the name of the single .csv file to be + \item{\code{makecsv}}{A character vector for the name of the single .csv file to be made for all simulated "subjects". If no file extension is included, ".csv" will be added, e.g. "simout" will become "simout.csv". If an extension is included, Pmetrics will use it, e.g. "simout.ssv" will save under that name. @@ -572,16 +560,14 @@ be of the form \emph{id_nsim}, e.g. 1_1 through 1_10 through for the first data template id, 2_1 through 2_10 for the second template id, etc. if 10 simulations are made from each subject in the data template.} - -\item{\code{quiet}}{Boolean operator controlling whether a model summary report is + \item{\code{quiet}}{Boolean operator controlling whether a model summary report is given. Default is \code{FALSE}.} - -\item{\code{...}}{Catch deprecated arguments.} -} -\if{html}{\out{}} -} -\subsection{Details}{ -The Monte Carlo simulator in Pmetrics generates randomly sampled sets of + \item{\code{...}}{Catch deprecated arguments.} + } + \if{html}{\out{}} + } + \subsection{Details}{ + The Monte Carlo simulator in Pmetrics generates randomly sampled sets of parameters from the \emph{PRIMARY} block of a model according to a prior distribution and calculates outputs based upon a template data file. It is a powerful tool for parametric or @@ -631,15 +617,13 @@ parameter sets and corresponding observations. Simulator output is returned as a \link{PM_sim} object. Output may also be directed to a new Pmetrics .csv data file using the \code{makecsv} argument. -} - -\subsection{Returns}{ -A \link{PM_sim} object. -} -\subsection{Examples}{ -\if{html}{\out{
}} -\preformatted{\dontrun{ -# Load results of NPAG run + } + \subsection{Returns}{ + A \link{PM_sim} object. + } + \subsection{Examples}{ + \if{html}{\out{
}} + \preformatted{# Load results of NPAG run run1 <- PM_load(1) # Two methods to simulate @@ -684,180 +668,193 @@ sim2 <- mod$sim(poppar = poppar, data = "temp1.csv", nsim = 15, include = 1:4, limits = NA, noise = list(out = list(coeff = c(0.02, 0.1, 0, 0))) ) - -} - } -\if{html}{\out{
}} - + \if{html}{\out{
}} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_sim-save}{}}} -\subsection{Method \code{save()}}{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\subsection{\code{PM_sim$save()}}{ + \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} Save the current PM_sim object into a .rds file. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_sim$save(file_name = "PMsim.rds")}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_sim$save(file_name = "PMsim.rds")} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{file_name}}{Name of the file to be created, the default is PMsim.rds} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{file_name}}{Name of the file to be created, the default is PMsim.rds} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_sim-plot}{}}} -\subsection{Method \code{plot()}}{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\subsection{\code{PM_sim$plot()}}{ + \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} Plot \code{PM_sim} object. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_sim$plot(...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{plot.PM_sim}.} - -\item{\code{at}}{Index of the PM_sim object to be plotted. Default is 1. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_sim$plot(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{plot.PM_sim}.} + \item{\code{at}}{Index of the PM_sim object to be plotted. Default is 1. result.} + } + \if{html}{\out{
}} + } } -\if{html}{\out{
}} -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_sim-opt}{}}} -\subsection{Method \code{opt()}}{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\subsection{\code{PM_sim$opt()}}{ + \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} Calculate optimal sampling times -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_sim$opt(...)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_sim$opt(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Additional parameters, refer to \link{PM_opt}} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Additional parameters, refer to \link{PM_opt}} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_sim-pta}{}}} -\subsection{Method \code{pta()}}{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\subsection{\code{PM_sim$pta()}}{ + \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} Estimates the Probability of Target Attaintment (PTA), based on the results of the current Simulation. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_sim$pta(...)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_sim$pta(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Additional parameters, refer to \link{PM_pta}.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Additional parameters, refer to \link{PM_pta}.} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_sim-auc}{}}} -\subsection{Method \code{auc()}}{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\subsection{\code{PM_sim$auc()}}{ + \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} Calculates the AUC of the specified simulation -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_sim$auc(...)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_sim$auc(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments passed to \link{make_AUC}.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments passed to \link{make_AUC}.} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_sim-summary}{}}} -\subsection{Method \code{summary()}}{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\subsection{\code{PM_sim$summary()}}{ + \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} Summarize simulation -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_sim$summary(...)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_sim$summary(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Parameters passed to \link{summary.PM_sim}.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Parameters passed to \link{summary.PM_sim}.} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_sim-run}{}}} -\subsection{Method \code{run()}}{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +\subsection{\code{PM_sim$run()}}{ + \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Deprecated method to run a simulation. Replaced by \code{PM_sim$new()} to be consistent with R6. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_sim$run(...)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_sim$run(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Not used.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Not used.} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_sim-load}{}}} -\subsection{Method \code{load()}}{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +\subsection{\code{PM_sim$load()}}{ + \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Deprecated method to load a prior simulation. Replaced by \code{PM_sim$new()} to be consistent with R6. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_sim$load(...)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_sim$load(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Not used.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Not used.} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_sim-clone}{}}} -\subsection{Method \code{clone()}}{ -The objects of this class are cloneable with this method. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_sim$clone(deep = FALSE)}\if{html}{\out{
}} +\subsection{\code{PM_sim$clone()}}{ + The objects of this class are cloneable with this method. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_sim$clone(deep = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{deep}}{Whether to make a deep clone.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{deep}}{Whether to make a deep clone.} -} -\if{html}{\out{
}} -} -} } diff --git a/man/PM_valid.Rd b/man/PM_valid.Rd index 1b6d9b161..cb4aba14d 100755 --- a/man/PM_valid.Rd +++ b/man/PM_valid.Rd @@ -24,7 +24,7 @@ themselves. \examples{ ## ------------------------------------------------ -## Method `PM_valid$new` +## Method `PM_valid$new()` ## ------------------------------------------------ \dontrun{ @@ -39,79 +39,73 @@ valid <- NPex$validate(limits = c(0, 3)) Michael Neely } \section{Public fields}{ -\if{html}{\out{
}} -\describe{ -\item{\code{simdata}}{Simulated data created in the validation process} + \if{html}{\out{
}} + \describe{ + \item{\code{simdata}}{Simulated data created in the validation process} -\item{\code{timeBinMedian}}{Median times for cluster bins} + \item{\code{timeBinMedian}}{Median times for cluster bins} -\item{\code{tadBinMedian}}{Median times after previous doses for cluster bins} + \item{\code{tadBinMedian}}{Median times after previous doses for cluster bins} -\item{\code{opDF}}{Observed-predicted data frame} + \item{\code{opDF}}{Observed-predicted data frame} -\item{\code{npde}}{Data for Normalized Prediction Distribution Error} + \item{\code{npde}}{Data for Normalized Prediction Distribution Error} -\item{\code{npde_tad}}{Data for Normalized Prediction Distribution Error + \item{\code{npde_tad}}{Data for Normalized Prediction Distribution Error using Time After Dose if available} -\item{\code{npde_stats}}{Captured NPDE console summaries for later replay} -} -\if{html}{\out{
}} + \item{\code{npde_stats}}{Captured NPDE console summaries for later replay} + } + \if{html}{\out{
}} } \section{Methods}{ \subsection{Public methods}{ -\itemize{ -\item \href{#method-PM_valid-new}{\code{PM_valid$new()}} -\item \href{#method-PM_valid-show_npde_stats}{\code{PM_valid$show_npde_stats()}} -\item \href{#method-PM_valid-summary}{\code{PM_valid$summary()}} -\item \href{#method-PM_valid-plot}{\code{PM_valid$plot()}} -\item \href{#method-PM_valid-clone}{\code{PM_valid$clone()}} -} + \itemize{ + \item \href{#method-PM_valid-initialize}{\code{PM_valid$new()}} + \item \href{#method-PM_valid-show_npde_stats}{\code{PM_valid$show_npde_stats()}} + \item \href{#method-PM_valid-summary}{\code{PM_valid$summary()}} + \item \href{#method-PM_valid-plot}{\code{PM_valid$plot()}} + \item \href{#method-PM_valid-clone}{\code{PM_valid$clone()}} + } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-PM_valid-new}{}}} -\subsection{Method \code{new()}}{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PM_valid-initialize}{}}} +\subsection{\code{PM_valid$new()}}{ + \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} This function will create an object suitable for plotting visual predictive checks (VPCs) and prediction-corrected visual predictive checks (pcVPCs). -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_valid$new(result, tad = FALSE, binCov, doseC, timeC, tadC, limits, ...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{result}}{The result of a prior run, usually supplied by calling the + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_valid$new(result, tad = FALSE, binCov, doseC, timeC, tadC, limits, ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{result}}{The result of a prior run, usually supplied by calling the \verb{$validate()} method of a \link{PM_result} at the end of a run, or later loaded with \link{PM_load}.} - -\item{\code{tad}}{Boolean operator to use time after dose rather than time after start. Default is \code{FALSE}.} - -\item{\code{binCov}}{A character vector of the names of covariates which are included in the model, i.e. in the + \item{\code{tad}}{Boolean operator to use time after dose rather than time after start. Default is \code{FALSE}.} + \item{\code{binCov}}{A character vector of the names of covariates which are included in the model, i.e. in the model equations and which need to be binned. For example \code{binCov='wt'} if "wt" is included in a model equation like V=V0*wt, or \code{binCov=c( 'wt', 'crcl')} if both "wt" and "crcl" are included in model equations.} - -\item{\code{doseC}}{An integer with the number of dose/covariate bins to cluster, if known from a previous run of + \item{\code{doseC}}{An integer with the number of dose/covariate bins to cluster, if known from a previous run of this function. Including this value will skip the clustering portion for doses/covariates.} - -\item{\code{timeC}}{An integer with the number of observation time bins to cluster, if known from a previous run of + \item{\code{timeC}}{An integer with the number of observation time bins to cluster, if known from a previous run of this function. Including this value will skip the clustering portion for observation times.} - -\item{\code{tadC}}{An integer with the number of time after dose bins to cluster, if known from a previous run of + \item{\code{tadC}}{An integer with the number of time after dose bins to cluster, if known from a previous run of this function. Including this value will skip the clustering portion for time after dose. This argument will be ignored if \code{tad=FALSE}.} - -\item{\code{limits}}{Limits on simulated parameters. See \link{PM_sim}.} - -\item{\code{...}}{Other parameters to be passed to \link{PM_sim}, especially \code{limits}.} -} -\if{html}{\out{
}} -} -\subsection{Details}{ -The function will guide the user + \item{\code{limits}}{Limits on simulated parameters. See \link{PM_sim}.} + \item{\code{...}}{Other parameters to be passed to \link{PM_sim}, especially \code{limits}.} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + The function will guide the user through appropriate clustering of doses, covariates and sample times for prediction correction using the methods of Bergstrand et al (2011). \emph{NOTE:} Including \code{tad} is only @@ -122,10 +116,9 @@ sampling occurs after the average concentrations are the same from dose to dose. Otherwise observations are \emph{NOT} superimposable and \code{tad} should \emph{NOT} be used, i.e. should be set to \code{FALSE}. -} - -\subsection{Returns}{ -An R6 object of class \code{PM_valid}, which is a list with the following. + } + \subsection{Returns}{ + An R6 object of class \code{PM_valid}, which is a list with the following. \itemize{ \item simdata The combined, simulated files for all subjects using the population mean values and each subject as a template. This object will be automatically saved to the run, to be loaded with @@ -138,90 +131,93 @@ This will be \code{NA} if \code{tad = FALSE}. \item npde_tad NPDE with time after dose rather than absolute time, if \code{tad = TRUE} \item npde_stats Captured NPDE console summary text, replayable with \verb{$show_npde_stats()}. } + } + \subsection{Examples}{ + \if{html}{\out{
}} + \preformatted{valid <- NPex$validate(limits = c(0, 3)) } -\subsection{Examples}{ -\if{html}{\out{
}} -\preformatted{\dontrun{ -valid <- NPex$validate(limits = c(0, 3)) -} - -} -\if{html}{\out{
}} - + \if{html}{\out{
}} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_valid-show_npde_stats}{}}} -\subsection{Method \code{show_npde_stats()}}{ -Replay captured NPDE summary statistics. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_valid$show_npde_stats(outeq = 1, tad = FALSE)}\if{html}{\out{
}} +\subsection{\code{PM_valid$show_npde_stats()}}{ + Replay captured NPDE summary statistics. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_valid$show_npde_stats(outeq = 1, tad = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{outeq}}{Output equation number to replay.} + \item{\code{tad}}{Logical; replay TAD-based NPDE stats.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{outeq}}{Output equation number to replay.} - -\item{\code{tad}}{Logical; replay TAD-based NPDE stats.} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_valid-summary}{}}} -\subsection{Method \code{summary()}}{ -Summarize a \link{PM_valid} object. Reports NPDE distribution statistics and a +\subsection{\code{PM_valid$summary()}}{ + Summarize a \link{PM_valid} object. Reports NPDE distribution statistics and a Numerical Predictive Check (NPC) for each output equation. TAD-based results are included automatically when TAD validation was performed. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_valid$summary(probs = c(0.05, 0.5, 0.95))}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{probs}}{Numeric vector of quantile cut-points for the NPC. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_valid$summary(probs = c(0.05, 0.5, 0.95))} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{probs}}{Numeric vector of quantile cut-points for the NPC. Default is \code{c(0.05, 0.5, 0.95)}.} + } + \if{html}{\out{
}} + } } -\if{html}{\out{
}} -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_valid-plot}{}}} -\subsection{Method \code{plot()}}{ -Plot method. Calls \link{plot.PM_valid}. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_valid$plot(...)}\if{html}{\out{
}} +\subsection{\code{PM_valid$plot()}}{ + Plot method. Calls \link{plot.PM_valid}. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_valid$plot(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{Arguments to pass to [plot.PM_valid].} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Arguments to pass to [plot.PM_valid].} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_valid-clone}{}}} -\subsection{Method \code{clone()}}{ -The objects of this class are cloneable with this method. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{PM_valid$clone(deep = FALSE)}\if{html}{\out{
}} +\subsection{\code{PM_valid$clone()}}{ + The objects of this class are cloneable with this method. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{PM_valid$clone(deep = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{deep}}{Whether to make a deep clone.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{deep}}{Whether to make a deep clone.} -} -\if{html}{\out{
}} -} -} } diff --git a/man/mic1.Rd b/man/mic1.Rd index 148c999b7..b9e744d24 100755 --- a/man/mic1.Rd +++ b/man/mic1.Rd @@ -24,7 +24,7 @@ or drug, and then select the desired row of the resulting table to see a histogram (top) and table (bottom) of MIC distributions. Copy the table into excel, save as a .csv file, and read into R using a function like -\code{\link[=read.csv]{read.csv()}} or \code{\link[readr:read_delim]{readr::read_csv()}}. Then use \code{\link[=makePTAtarget]{makePTAtarget()}}. +\code{\link[=read.csv]{read.csv()}} or \code{\link[readr:read_csv]{readr::read_csv()}}. Then use \code{\link[=makePTAtarget]{makePTAtarget()}}. } \author{ Michael Neely diff --git a/man/plot.PM_cov.Rd b/man/plot.PM_cov.Rd index 5ba2b2e29..4a9dd31a4 100755 --- a/man/plot.PM_cov.Rd +++ b/man/plot.PM_cov.Rd @@ -171,20 +171,20 @@ NPex$cov$plot(V ~ wt, } } \seealso{ -\link{PM_cov}, \link{PM_result}, \link[plotly:schema]{plotly::schema} - -Other PMplots: -\code{\link{plot.PM_cycle}()}, -\code{\link{plot.PM_data}()}, -\code{\link{plot.PM_final}()}, -\code{\link{plot.PM_model}()}, -\code{\link{plot.PM_op}()}, -\code{\link{plot.PM_opt}()}, -\code{\link{plot.PM_pop}()}, -\code{\link{plot.PM_post}()}, -\code{\link{plot.PM_pta}()}, -\code{\link{plot.PM_sim}()}, -\code{\link{plot.PM_valid}()} +\link{PM_cov}, \link{PM_result}, \link[plotly:schema]{schema} + +Other PMplots: +\code{\link[=plot.PM_cycle]{plot.PM_cycle()}}, +\code{\link[=plot.PM_data]{plot.PM_data()}}, +\code{\link[=plot.PM_final]{plot.PM_final()}}, +\code{\link[=plot.PM_model]{plot.PM_model()}}, +\code{\link[=plot.PM_op]{plot.PM_op()}}, +\code{\link[=plot.PM_opt]{plot.PM_opt()}}, +\code{\link[=plot.PM_pop]{plot.PM_pop()}}, +\code{\link[=plot.PM_post]{plot.PM_post()}}, +\code{\link[=plot.PM_pta]{plot.PM_pta()}}, +\code{\link[=plot.PM_sim]{plot.PM_sim()}}, +\code{\link[=plot.PM_valid]{plot.PM_valid()}} } \author{ Michael Neely diff --git a/man/plot.PM_cycle.Rd b/man/plot.PM_cycle.Rd index c666b6141..ec2899a65 100755 --- a/man/plot.PM_cycle.Rd +++ b/man/plot.PM_cycle.Rd @@ -114,20 +114,20 @@ NPex$cycle$plot( } } \seealso{ -\link{PM_result}, \link[plotly:schema]{plotly::schema} +\link{PM_result}, \link[plotly:schema]{schema} -Other PMplots: -\code{\link{plot.PM_cov}()}, -\code{\link{plot.PM_data}()}, -\code{\link{plot.PM_final}()}, -\code{\link{plot.PM_model}()}, -\code{\link{plot.PM_op}()}, -\code{\link{plot.PM_opt}()}, -\code{\link{plot.PM_pop}()}, -\code{\link{plot.PM_post}()}, -\code{\link{plot.PM_pta}()}, -\code{\link{plot.PM_sim}()}, -\code{\link{plot.PM_valid}()} +Other PMplots: +\code{\link[=plot.PM_cov]{plot.PM_cov()}}, +\code{\link[=plot.PM_data]{plot.PM_data()}}, +\code{\link[=plot.PM_final]{plot.PM_final()}}, +\code{\link[=plot.PM_model]{plot.PM_model()}}, +\code{\link[=plot.PM_op]{plot.PM_op()}}, +\code{\link[=plot.PM_opt]{plot.PM_opt()}}, +\code{\link[=plot.PM_pop]{plot.PM_pop()}}, +\code{\link[=plot.PM_post]{plot.PM_post()}}, +\code{\link[=plot.PM_pta]{plot.PM_pta()}}, +\code{\link[=plot.PM_sim]{plot.PM_sim()}}, +\code{\link[=plot.PM_valid]{plot.PM_valid()}} } \author{ Michael Neely diff --git a/man/plot.PM_data.Rd b/man/plot.PM_data.Rd index 4ae0fdd4e..30c4dc428 100755 --- a/man/plot.PM_data.Rd +++ b/man/plot.PM_data.Rd @@ -208,18 +208,18 @@ dataEx$plot( \seealso{ \link{PM_data}, \link{PM_result} -Other PMplots: -\code{\link{plot.PM_cov}()}, -\code{\link{plot.PM_cycle}()}, -\code{\link{plot.PM_final}()}, -\code{\link{plot.PM_model}()}, -\code{\link{plot.PM_op}()}, -\code{\link{plot.PM_opt}()}, -\code{\link{plot.PM_pop}()}, -\code{\link{plot.PM_post}()}, -\code{\link{plot.PM_pta}()}, -\code{\link{plot.PM_sim}()}, -\code{\link{plot.PM_valid}()} +Other PMplots: +\code{\link[=plot.PM_cov]{plot.PM_cov()}}, +\code{\link[=plot.PM_cycle]{plot.PM_cycle()}}, +\code{\link[=plot.PM_final]{plot.PM_final()}}, +\code{\link[=plot.PM_model]{plot.PM_model()}}, +\code{\link[=plot.PM_op]{plot.PM_op()}}, +\code{\link[=plot.PM_opt]{plot.PM_opt()}}, +\code{\link[=plot.PM_pop]{plot.PM_pop()}}, +\code{\link[=plot.PM_post]{plot.PM_post()}}, +\code{\link[=plot.PM_pta]{plot.PM_pta()}}, +\code{\link[=plot.PM_sim]{plot.PM_sim()}}, +\code{\link[=plot.PM_valid]{plot.PM_valid()}} } \author{ Michael Neely diff --git a/man/plot.PM_final.Rd b/man/plot.PM_final.Rd index 1ee0d6e7e..1220e5f7c 100755 --- a/man/plot.PM_final.Rd +++ b/man/plot.PM_final.Rd @@ -160,20 +160,20 @@ ITex$final$plot(Ke ~ V) } } \seealso{ -\link{PM_final}, \link[plotly:schema]{plotly::schema} - -Other PMplots: -\code{\link{plot.PM_cov}()}, -\code{\link{plot.PM_cycle}()}, -\code{\link{plot.PM_data}()}, -\code{\link{plot.PM_model}()}, -\code{\link{plot.PM_op}()}, -\code{\link{plot.PM_opt}()}, -\code{\link{plot.PM_pop}()}, -\code{\link{plot.PM_post}()}, -\code{\link{plot.PM_pta}()}, -\code{\link{plot.PM_sim}()}, -\code{\link{plot.PM_valid}()} +\link{PM_final}, \link[plotly:schema]{schema} + +Other PMplots: +\code{\link[=plot.PM_cov]{plot.PM_cov()}}, +\code{\link[=plot.PM_cycle]{plot.PM_cycle()}}, +\code{\link[=plot.PM_data]{plot.PM_data()}}, +\code{\link[=plot.PM_model]{plot.PM_model()}}, +\code{\link[=plot.PM_op]{plot.PM_op()}}, +\code{\link[=plot.PM_opt]{plot.PM_opt()}}, +\code{\link[=plot.PM_pop]{plot.PM_pop()}}, +\code{\link[=plot.PM_post]{plot.PM_post()}}, +\code{\link[=plot.PM_pta]{plot.PM_pta()}}, +\code{\link[=plot.PM_sim]{plot.PM_sim()}}, +\code{\link[=plot.PM_valid]{plot.PM_valid()}} } \author{ Michael Neely diff --git a/man/plot.PM_model.Rd b/man/plot.PM_model.Rd index 6adc99ec2..0249fb452 100755 --- a/man/plot.PM_model.Rd +++ b/man/plot.PM_model.Rd @@ -74,18 +74,18 @@ NPex$model$plot() \seealso{ \link{PM_model}, \code{\link[ggplot2:ggplot]{ggplot2::ggplot()}} -Other PMplots: -\code{\link{plot.PM_cov}()}, -\code{\link{plot.PM_cycle}()}, -\code{\link{plot.PM_data}()}, -\code{\link{plot.PM_final}()}, -\code{\link{plot.PM_op}()}, -\code{\link{plot.PM_opt}()}, -\code{\link{plot.PM_pop}()}, -\code{\link{plot.PM_post}()}, -\code{\link{plot.PM_pta}()}, -\code{\link{plot.PM_sim}()}, -\code{\link{plot.PM_valid}()} +Other PMplots: +\code{\link[=plot.PM_cov]{plot.PM_cov()}}, +\code{\link[=plot.PM_cycle]{plot.PM_cycle()}}, +\code{\link[=plot.PM_data]{plot.PM_data()}}, +\code{\link[=plot.PM_final]{plot.PM_final()}}, +\code{\link[=plot.PM_op]{plot.PM_op()}}, +\code{\link[=plot.PM_opt]{plot.PM_opt()}}, +\code{\link[=plot.PM_pop]{plot.PM_pop()}}, +\code{\link[=plot.PM_post]{plot.PM_post()}}, +\code{\link[=plot.PM_pta]{plot.PM_pta()}}, +\code{\link[=plot.PM_sim]{plot.PM_sim()}}, +\code{\link[=plot.PM_valid]{plot.PM_valid()}} } \author{ Markus Hovd, Julian Otalvaro, Michael Neely diff --git a/man/plot.PM_op.Rd b/man/plot.PM_op.Rd index 4961763dd..bf86c132f 100755 --- a/man/plot.PM_op.Rd +++ b/man/plot.PM_op.Rd @@ -187,20 +187,20 @@ NPex$op$plot(stats = list(x = 0.5, y = 0.2, font = list(size = 7, color = "blue" } } \seealso{ -\link{PM_result}, \link{PM_op}, \link[plotly:schema]{plotly::schema} - -Other PMplots: -\code{\link{plot.PM_cov}()}, -\code{\link{plot.PM_cycle}()}, -\code{\link{plot.PM_data}()}, -\code{\link{plot.PM_final}()}, -\code{\link{plot.PM_model}()}, -\code{\link{plot.PM_opt}()}, -\code{\link{plot.PM_pop}()}, -\code{\link{plot.PM_post}()}, -\code{\link{plot.PM_pta}()}, -\code{\link{plot.PM_sim}()}, -\code{\link{plot.PM_valid}()} +\link{PM_result}, \link{PM_op}, \link[plotly:schema]{schema} + +Other PMplots: +\code{\link[=plot.PM_cov]{plot.PM_cov()}}, +\code{\link[=plot.PM_cycle]{plot.PM_cycle()}}, +\code{\link[=plot.PM_data]{plot.PM_data()}}, +\code{\link[=plot.PM_final]{plot.PM_final()}}, +\code{\link[=plot.PM_model]{plot.PM_model()}}, +\code{\link[=plot.PM_opt]{plot.PM_opt()}}, +\code{\link[=plot.PM_pop]{plot.PM_pop()}}, +\code{\link[=plot.PM_post]{plot.PM_post()}}, +\code{\link[=plot.PM_pta]{plot.PM_pta()}}, +\code{\link[=plot.PM_sim]{plot.PM_sim()}}, +\code{\link[=plot.PM_valid]{plot.PM_valid()}} } \author{ Michael Neely diff --git a/man/plot.PM_opt.Rd b/man/plot.PM_opt.Rd index 8b627be62..8cc720352 100755 --- a/man/plot.PM_opt.Rd +++ b/man/plot.PM_opt.Rd @@ -37,18 +37,18 @@ for \link{plot.PM_sim}. \seealso{ \link{plot.PM_sim} -Other PMplots: -\code{\link{plot.PM_cov}()}, -\code{\link{plot.PM_cycle}()}, -\code{\link{plot.PM_data}()}, -\code{\link{plot.PM_final}()}, -\code{\link{plot.PM_model}()}, -\code{\link{plot.PM_op}()}, -\code{\link{plot.PM_pop}()}, -\code{\link{plot.PM_post}()}, -\code{\link{plot.PM_pta}()}, -\code{\link{plot.PM_sim}()}, -\code{\link{plot.PM_valid}()} +Other PMplots: +\code{\link[=plot.PM_cov]{plot.PM_cov()}}, +\code{\link[=plot.PM_cycle]{plot.PM_cycle()}}, +\code{\link[=plot.PM_data]{plot.PM_data()}}, +\code{\link[=plot.PM_final]{plot.PM_final()}}, +\code{\link[=plot.PM_model]{plot.PM_model()}}, +\code{\link[=plot.PM_op]{plot.PM_op()}}, +\code{\link[=plot.PM_pop]{plot.PM_pop()}}, +\code{\link[=plot.PM_post]{plot.PM_post()}}, +\code{\link[=plot.PM_pta]{plot.PM_pta()}}, +\code{\link[=plot.PM_sim]{plot.PM_sim()}}, +\code{\link[=plot.PM_valid]{plot.PM_valid()}} } \author{ Michael Neely diff --git a/man/plot.PM_pop.Rd b/man/plot.PM_pop.Rd index b562173eb..2563128f5 100755 --- a/man/plot.PM_pop.Rd +++ b/man/plot.PM_pop.Rd @@ -152,18 +152,18 @@ NPex$pop$plot( \seealso{ \link{PM_pop}, \link{PM_result} -Other PMplots: -\code{\link{plot.PM_cov}()}, -\code{\link{plot.PM_cycle}()}, -\code{\link{plot.PM_data}()}, -\code{\link{plot.PM_final}()}, -\code{\link{plot.PM_model}()}, -\code{\link{plot.PM_op}()}, -\code{\link{plot.PM_opt}()}, -\code{\link{plot.PM_post}()}, -\code{\link{plot.PM_pta}()}, -\code{\link{plot.PM_sim}()}, -\code{\link{plot.PM_valid}()} +Other PMplots: +\code{\link[=plot.PM_cov]{plot.PM_cov()}}, +\code{\link[=plot.PM_cycle]{plot.PM_cycle()}}, +\code{\link[=plot.PM_data]{plot.PM_data()}}, +\code{\link[=plot.PM_final]{plot.PM_final()}}, +\code{\link[=plot.PM_model]{plot.PM_model()}}, +\code{\link[=plot.PM_op]{plot.PM_op()}}, +\code{\link[=plot.PM_opt]{plot.PM_opt()}}, +\code{\link[=plot.PM_post]{plot.PM_post()}}, +\code{\link[=plot.PM_pta]{plot.PM_pta()}}, +\code{\link[=plot.PM_sim]{plot.PM_sim()}}, +\code{\link[=plot.PM_valid]{plot.PM_valid()}} } \author{ Michael Neely diff --git a/man/plot.PM_post.Rd b/man/plot.PM_post.Rd index a6a929993..fab0d3b30 100755 --- a/man/plot.PM_post.Rd +++ b/man/plot.PM_post.Rd @@ -151,18 +151,18 @@ NPex$post$plot( \seealso{ \link{PM_post}, \link{PM_result} -Other PMplots: -\code{\link{plot.PM_cov}()}, -\code{\link{plot.PM_cycle}()}, -\code{\link{plot.PM_data}()}, -\code{\link{plot.PM_final}()}, -\code{\link{plot.PM_model}()}, -\code{\link{plot.PM_op}()}, -\code{\link{plot.PM_opt}()}, -\code{\link{plot.PM_pop}()}, -\code{\link{plot.PM_pta}()}, -\code{\link{plot.PM_sim}()}, -\code{\link{plot.PM_valid}()} +Other PMplots: +\code{\link[=plot.PM_cov]{plot.PM_cov()}}, +\code{\link[=plot.PM_cycle]{plot.PM_cycle()}}, +\code{\link[=plot.PM_data]{plot.PM_data()}}, +\code{\link[=plot.PM_final]{plot.PM_final()}}, +\code{\link[=plot.PM_model]{plot.PM_model()}}, +\code{\link[=plot.PM_op]{plot.PM_op()}}, +\code{\link[=plot.PM_opt]{plot.PM_opt()}}, +\code{\link[=plot.PM_pop]{plot.PM_pop()}}, +\code{\link[=plot.PM_pta]{plot.PM_pta()}}, +\code{\link[=plot.PM_sim]{plot.PM_sim()}}, +\code{\link[=plot.PM_valid]{plot.PM_valid()}} } \author{ Michael Neely diff --git a/man/plot.PM_pta.Rd b/man/plot.PM_pta.Rd index 4b7e08351..03ce271b2 100755 --- a/man/plot.PM_pta.Rd +++ b/man/plot.PM_pta.Rd @@ -61,7 +61,7 @@ the \code{line} argument should be a list of the options for group based plottin where each group corresponds to a simulated regimen. The possible elements of the \code{line} list should be exactly named: \itemize{ -\item color Maps to the \link[plotly:plot_ly]{plotly::plot_ly} \code{colors} argument to override default colors +\item color Maps to the \link[plotly:plot_ly]{plot_ly} \code{colors} argument to override default colors applied to the lines for each regimen. This can be a named palette, which can be obtained with \code{RColorBrewer::display.brewer.all()} or a vector of hexadecimal color names. One way to ensure reliable color palettes is to use the @@ -72,12 +72,12 @@ of JavaScript on the ColorBrewer website. The default is "Set1". Palettes with fewer colors than regimens will be recycled. A color can also be a character vector of color names, recycled as needed. For example, a print-friendly choice is \code{line = list(color = "black")}. -\item width Maps to the \link[plotly:plot_ly]{plotly::plot_ly} \code{width} argument to override default widths +\item width Maps to the \link[plotly:plot_ly]{plot_ly} \code{width} argument to override default widths applied to the lines for each regimen. All lines will have the same width. The default value is 2. -\item dash Maps to the \link[plotly:plot_ly]{plotly::plot_ly} \code{linetypes} argument to override default styles +\item dash Maps to the \link[plotly:plot_ly]{plot_ly} \code{linetypes} argument to override default styles applied to the lines for each regimen. If numeric, will map to \code{lty} \link{par} values. -It can also be a character vector of dash names as listed in \link[plotly:plot_ly]{plotly::plot_ly}. +It can also be a character vector of dash names as listed in \link[plotly:plot_ly]{plot_ly}. Example: \code{line = list(color = "Blues", width = 1, dash = 2)}, which will result in dotted lines (dash = 2) all with width 1 but in different shades of blue. }} @@ -97,12 +97,12 @@ where each group corresponds to a simulated regimen. The possible elements of th marker color does not need to also be specified. Even if line plotting is suppressed with \code{line = F}, the default color value of "Set1" will be applied to markers, unless specified, e.g. \code{marker = list(color = "Blues")}. -\item symbol Maps to the \link[plotly:plot_ly]{plotly::plot_ly} \code{symbols} argument to override default symbols +\item symbol Maps to the \link[plotly:plot_ly]{plot_ly} \code{symbols} argument to override default symbols applied to the markers for each regimen. If only one value is supplied for this, it will be recycled for each regimen, i.e. all will have the same symbol. See \code{plotly::schema()}, traces > scatter > attributes > marker > symbol > values for options. -\item size Maps to the \link[plotly:plot_ly]{plotly::plot_ly} \code{size} argument to override default size +\item size Maps to the \link[plotly:plot_ly]{plot_ly} \code{size} argument to override default size applied to the markers for each regimen. All markers will have the same size. The default value is 12. }} @@ -182,18 +182,18 @@ pta1$plot() } } \seealso{ -Other PMplots: -\code{\link{plot.PM_cov}()}, -\code{\link{plot.PM_cycle}()}, -\code{\link{plot.PM_data}()}, -\code{\link{plot.PM_final}()}, -\code{\link{plot.PM_model}()}, -\code{\link{plot.PM_op}()}, -\code{\link{plot.PM_opt}()}, -\code{\link{plot.PM_pop}()}, -\code{\link{plot.PM_post}()}, -\code{\link{plot.PM_sim}()}, -\code{\link{plot.PM_valid}()} +Other PMplots: +\code{\link[=plot.PM_cov]{plot.PM_cov()}}, +\code{\link[=plot.PM_cycle]{plot.PM_cycle()}}, +\code{\link[=plot.PM_data]{plot.PM_data()}}, +\code{\link[=plot.PM_final]{plot.PM_final()}}, +\code{\link[=plot.PM_model]{plot.PM_model()}}, +\code{\link[=plot.PM_op]{plot.PM_op()}}, +\code{\link[=plot.PM_opt]{plot.PM_opt()}}, +\code{\link[=plot.PM_pop]{plot.PM_pop()}}, +\code{\link[=plot.PM_post]{plot.PM_post()}}, +\code{\link[=plot.PM_sim]{plot.PM_sim()}}, +\code{\link[=plot.PM_valid]{plot.PM_valid()}} } \author{ Michael Neely diff --git a/man/plot.PM_sim.Rd b/man/plot.PM_sim.Rd index fb54fdb11..78886fe8e 100755 --- a/man/plot.PM_sim.Rd +++ b/man/plot.PM_sim.Rd @@ -196,20 +196,20 @@ simEx$plot(log = FALSE, line = list(color = "orange")) } } \seealso{ -\link{PM_sim}, \link[plotly:plot_ly]{plotly::plot_ly}, \link[plotly:schema]{plotly::schema} - -Other PMplots: -\code{\link{plot.PM_cov}()}, -\code{\link{plot.PM_cycle}()}, -\code{\link{plot.PM_data}()}, -\code{\link{plot.PM_final}()}, -\code{\link{plot.PM_model}()}, -\code{\link{plot.PM_op}()}, -\code{\link{plot.PM_opt}()}, -\code{\link{plot.PM_pop}()}, -\code{\link{plot.PM_post}()}, -\code{\link{plot.PM_pta}()}, -\code{\link{plot.PM_valid}()} +\link{PM_sim}, \link[plotly:plot_ly]{plot_ly}, \link[plotly:schema]{schema} + +Other PMplots: +\code{\link[=plot.PM_cov]{plot.PM_cov()}}, +\code{\link[=plot.PM_cycle]{plot.PM_cycle()}}, +\code{\link[=plot.PM_data]{plot.PM_data()}}, +\code{\link[=plot.PM_final]{plot.PM_final()}}, +\code{\link[=plot.PM_model]{plot.PM_model()}}, +\code{\link[=plot.PM_op]{plot.PM_op()}}, +\code{\link[=plot.PM_opt]{plot.PM_opt()}}, +\code{\link[=plot.PM_pop]{plot.PM_pop()}}, +\code{\link[=plot.PM_post]{plot.PM_post()}}, +\code{\link[=plot.PM_pta]{plot.PM_pta()}}, +\code{\link[=plot.PM_valid]{plot.PM_valid()}} } \author{ Michael Neely diff --git a/man/plot.PM_valid.Rd b/man/plot.PM_valid.Rd index e973c3e5f..c96c67bdd 100755 --- a/man/plot.PM_valid.Rd +++ b/man/plot.PM_valid.Rd @@ -171,18 +171,18 @@ NPex$valid$plot( \seealso{ \link{make_valid} -Other PMplots: -\code{\link{plot.PM_cov}()}, -\code{\link{plot.PM_cycle}()}, -\code{\link{plot.PM_data}()}, -\code{\link{plot.PM_final}()}, -\code{\link{plot.PM_model}()}, -\code{\link{plot.PM_op}()}, -\code{\link{plot.PM_opt}()}, -\code{\link{plot.PM_pop}()}, -\code{\link{plot.PM_post}()}, -\code{\link{plot.PM_pta}()}, -\code{\link{plot.PM_sim}()} +Other PMplots: +\code{\link[=plot.PM_cov]{plot.PM_cov()}}, +\code{\link[=plot.PM_cycle]{plot.PM_cycle()}}, +\code{\link[=plot.PM_data]{plot.PM_data()}}, +\code{\link[=plot.PM_final]{plot.PM_final()}}, +\code{\link[=plot.PM_model]{plot.PM_model()}}, +\code{\link[=plot.PM_op]{plot.PM_op()}}, +\code{\link[=plot.PM_opt]{plot.PM_opt()}}, +\code{\link[=plot.PM_pop]{plot.PM_pop()}}, +\code{\link[=plot.PM_post]{plot.PM_post()}}, +\code{\link[=plot.PM_pta]{plot.PM_pta()}}, +\code{\link[=plot.PM_sim]{plot.PM_sim()}} } \author{ Michael Neely diff --git a/man/plot.PMvalid.Rd b/man/plot.PMvalid.Rd index faf056287..64541f4d3 100755 --- a/man/plot.PMvalid.Rd +++ b/man/plot.PMvalid.Rd @@ -58,7 +58,7 @@ See \code{\link{points}} for other values of \code{pch}.} \item{data_theme}{Default is \dQuote{color}, but could be \dQuote{grey} or \dQuote{gray}.} \item{plot_theme}{Default is \code{theme_grey()} but could be any complete ggplot2 theme, e.g. -\code{\link[ggplot2:ggtheme]{ggplot2::theme_minimal()}}.} +\code{\link[ggplot2:theme_minimal]{ggplot2::theme_minimal()}}.} \item{col.obs.ci}{Color of the observation confidence interval (set by \code{lower} and \code{upper}). Default is blue.} diff --git a/man/round2.Rd b/man/round2.Rd index 7de05237f..d2b7d9b93 100755 --- a/man/round2.Rd +++ b/man/round2.Rd @@ -19,6 +19,6 @@ A character string representing the rounded value with the specified number of d Rounds a numeric value to a specified number of digits for display in flextables and plots. } \details{ -Uses \link[base:format]{base::format} and \link[base:Round]{base::round} to round a numeric value to a specified number of digits. +Uses \link[base:format]{base::format} and \link[base:round]{base::round} to round a numeric value to a specified number of digits. } \keyword{internal} From 35bcd0d5c768961d7df6f2a6e48e893a4e396802 Mon Sep 17 00:00:00 2001 From: Michael Neely Date: Mon, 1 Jun 2026 10:08:38 -0700 Subject: [PATCH 7/7] fix: some test failures; more to fix --- DESCRIPTION | 2 +- R/PM_report.R | 14 ++++++++-- R/PM_result.R | 11 ++++++++ _pkgdown.yml | 1 - man/PM_result.Rd | 16 +++++++++++ tests/testthat/Rplots.pdf | Bin 5440 -> 3611 bytes tests/testthat/test-engine-indices.R | 4 +++ tests/testthat/test-fit-log-controls.R | 4 +-- tests/testthat/test-fit-payload-result.R | 9 ++++-- tests/testthat/test-fit-progress-option.R | 4 +-- tests/testthat/test-fixed-error-models.R | 4 +-- tests/testthat/test-live-report-bridge.R | 14 ++-------- tests/testthat/test-live-report-handoff.R | 26 ++++++++++++++---- tests/testthat/test-pm-fit-prior-parameters.R | 2 +- tests/testthat/test-pm-report.R | 8 +++--- .../testthat/test-pmcore-pharmsol-migration.R | 2 +- 16 files changed, 85 insertions(+), 36 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 8c62c4d52..997425e72 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -89,4 +89,4 @@ LazyData: true LazyDataCompression: xz Roxygen: list(markdown = TRUE) SystemRequirements: Cargo (>= 1.82) (Rust's package manager), rustc -RoxygenNote: 7.3.3 + diff --git a/R/PM_report.R b/R/PM_report.R index ae476871f..98fd92884 100755 --- a/R/PM_report.R +++ b/R/PM_report.R @@ -41,7 +41,7 @@ PM_report <- function(x, template, path, show = TRUE, quiet = TRUE) { ][[1]] if (!is.null(dev_reports_path) && nzchar(dev_reports_path) && - requireNamespace("pkgload", quietly = TRUE)) { + pmetrics_require_namespace("pkgload", quietly = TRUE)) { loaded <- tryCatch( { pkgload::load_all(dev_reports_path, quiet = TRUE, export_all = FALSE) @@ -51,7 +51,7 @@ PM_report <- function(x, template, path, show = TRUE, quiet = TRUE) { ) if (isTRUE(loaded)) { - return(getExportedValue("PmetricsReports", "run_app")) + return(pmetrics_get_exported_value("PmetricsReports", "run_app")) } } @@ -59,7 +59,7 @@ PM_report <- function(x, template, path, show = TRUE, quiet = TRUE) { return(NULL) } - getExportedValue("PmetricsReports", "run_app") + pmetrics_get_exported_value("PmetricsReports", "run_app") } resolve_report_mode <- function() { @@ -186,3 +186,11 @@ PM_report <- function(x, template, path, show = TRUE, quiet = TRUE) { } ) } + + pmetrics_require_namespace <- function(package, quietly = FALSE) { + requireNamespace(package, quietly = quietly) + } + + pmetrics_get_exported_value <- function(pkg, name) { + getExportedValue(pkg, name) + } diff --git a/R/PM_result.R b/R/PM_result.R index b21095c99..7e589fe4b 100755 --- a/R/PM_result.R +++ b/R/PM_result.R @@ -538,6 +538,17 @@ update <- function(res, found) { return(res) } +#' @description +#' Export run results to standard output files. +#' +#' @details +#' Writes key run outputs (iterations, theta, posterior, predictions, +#' covariates, and settings) to the target folder. +#' +#' @param path Path to the output folder. Defaults to current working directory. +#' The folder is created if needed. +#' @return Invisibly returns `TRUE` when export completes. +#' @rdname PM_result # Export the result to standard output files without relying on legacy fit-time writes. PM_result$set("public", "export", function(path = ".") { path <- normalizePath(path, mustWork = FALSE) diff --git a/_pkgdown.yml b/_pkgdown.yml index b3a9865df..0c8f8b3dd 100755 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -44,7 +44,6 @@ reference: - PM_op - PM_pop - PM_opt - - PM_parse - PM_post - PM_pta - PM_sim diff --git a/man/PM_result.Rd b/man/PM_result.Rd index e8390c008..0bab923d0 100755 --- a/man/PM_result.Rd +++ b/man/PM_result.Rd @@ -410,11 +410,27 @@ as the prior for a new run. The data and model are the same as in the prior run. \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-PM_result-export}{}}} \subsection{\code{PM_result$export()}}{ + Export run results to standard output files. \subsection{Usage}{ \if{html}{\out{
}} \preformatted{PM_result$export(path = ".")} \if{html}{\out{
}} } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{path}}{Path to the output folder. Defaults to current working directory. +The folder is created if needed.} + } + \if{html}{\out{
}} + } + \subsection{Details}{ + Writes key run outputs (iterations, theta, posterior, predictions, +covariates, and settings) to the target folder. + } + \subsection{Returns}{ + Invisibly returns \code{TRUE} when export completes. + } } \if{html}{\out{
}} diff --git a/tests/testthat/Rplots.pdf b/tests/testthat/Rplots.pdf index 40ba8557b062c0d9871e3ec68fc4bd1efa5e0a5f..e82e11d17def2e0586a36a1ef6e5a0fe26806875 100644 GIT binary patch delta 207 zcmX@0HCtwaxr(uYp`nq9v4JL+zHfetOJYf?f`*Hgk%5t!fhkOGVzdsknV!MKF0aW) z*c7;874)6+OY=$;3?_eJ^VuB6{)CIse6h6V5-gr!=)`OO$Jz*=7DAy2XsjUuJW30;4P?Q9fvqVTg*HK9|6NP-IV6cSg3=P5csKwB(1K1Q%*_GN zGm;Jipj#k$XJo)s7Jw0#-2u>sMrDbYOaT9R?G7_(Yz7Ht0>(QVmX-*Z8oaHu-SAlt z0XoA}GAjgtFeZ2aw5NozV21b~CO(oaOrizD2un*Ig2`gQf#C>F&T!TbG+&%-f=J{Q zLIDfkZ4T&%eEp}8*nX*6nR=PUn6x27^=}Q<7NU;)oTtwoRzAXi7t|{JUOmxVCz7x{ zc~3A|vO34P1CIMt-&P}E$5=}FN#6EQBqWwPfJx8B`Qt1^Y^J6jAE>6|KPA_1ksESVhLrf*JiI6cQ)G>(K0YDV2omlib}Lekkf z!YXOK-FvoDK89?Ls(5dE zeFlVj=`HEjH^UG5kM(;Z$4(HzF@IvWavqyI)A>GPNJGSYL>bA=n~N@9Z--KN>=&3f zqpef-P1LNv$*h=%E!NsA80Y;4k~vBvWJa|-_uRFdgTfvS=dH*Z?Xk*fi$QLsw)v9h zvddf_w`}2M$Lc@o)*Q^2w6>JY$^^%LRVfoFT`XNwaBVnl)wOVEt#qT&9=TF%xwNvF zd#)vaK}Y@dTMeaw#9!s)@42<>@}A23|MMD#M>QJJ8u5=#v*+;4jwjO#{5nlM=Ma)! z9>?*Ftn$!A$@G_qU8>Dp1GjIuS>3;6XaokZPjFQ_Z4=d6zAAQK4^}J5r%)t)wtSvg zh@!0?DOc)yS;#m}<=09avb(;Nk?&0cx{liDYGKjJAG$nT{dX&N-)LJ?_O>2_vl?O z$~-SzI#iH|JXt+f#Jf4RpLYP=V|VqiN-l>Lv)TIBco!8zPJ^rU4cCDY!~VW4Ys2ig zTQ=9S<^C)(jw~>*oQ@Z4kbkL=)N6WO5R7=(@p@Ng$81tY7D>zS@;6JdHEkr*l#hcy zxg}Nkzl9bIv6uL>-Dx#%rSVwq?5BjiOyR3Y3Onl!65rB!lK#=he3a*3&EfZP9Fg?P z+5V#$IE!ZQR?}W9v&iC<^+C0U!y=Doj&ZPTbA9tNEPIce1LjCcQ;r1=oP||^$>7Ah zCD3h5s#FfX@#nbyg?|1s(^aI=BKPvULeT`3DM!dQhx z?fNm~{>IiU)A75!jpA%e&)|r@)I~!0n$y-vQoyHPi>K>-H!yfzqk9Uss;2G;pCy$M z$R9L)AC8YOhU)MUr?`O`IzOexhgNmP7ZtoQC@&>ERy0{1jul_PfQ)UBYDTWJpv@;B zgBUDb-bPdIKs4#6Mm_X`tsAsoEf2fWW%cZkc~iL_xELP)(qMaSbRk`)N!|ExK&W~& zvsb}e^HFK9M1!+#PfhzS4E1}+-OD?h@;o=ph{U|&6y#ClMpvkxS@gr@WH`pP*11rO z%jqkQSE|OGHSu?1ja-+W^gicYXraAh6{zjGphI;#D%gB6=NoWlhUi^#ktDPeV_-qK+J zRY%?^h-ww2YW`?+-=)#*=A4k9gk0$E#2H-Wr-D}JpOAGY29q?H>+g$R;-ECMLgcHzuIPFYu)V7KKKo acgbn&79VFuPXB8xghnIu^z2;i5&r_71$To0 diff --git a/tests/testthat/test-engine-indices.R b/tests/testthat/test-engine-indices.R index 9cb76d54d..77562c565 100644 --- a/tests/testthat/test-engine-indices.R +++ b/tests/testthat/test-engine-indices.R @@ -1,6 +1,8 @@ library(Pmetrics) test_that("decode_error_model_rows aligns observed outeq when models include leading placeholder", { + decode_error_model_rows <- getFromNamespace("decode_error_model_rows", "Pmetrics") + models <- list( "None", list(Proportional = list(poly = list(c0 = 0, c1 = 0.1, c2 = 0, c3 = 0))) @@ -15,6 +17,8 @@ test_that("decode_error_model_rows aligns observed outeq when models include lea }) test_that("decode_error_model_rows keeps direct alignment when no placeholder exists", { + decode_error_model_rows <- getFromNamespace("decode_error_model_rows", "Pmetrics") + models <- list( list(Additive = list(poly = list(c0 = 0.5, c1 = 0, c2 = 0, c3 = 0))), list(Proportional = list(poly = list(c0 = 0, c1 = 0.2, c2 = 0, c3 = 0))) diff --git a/tests/testthat/test-fit-log-controls.R b/tests/testthat/test-fit-log-controls.R index 4f9a57eb7..5040dd005 100644 --- a/tests/testthat/test-fit-log-controls.R +++ b/tests/testthat/test-fit-log-controls.R @@ -45,8 +45,8 @@ run_log_control_fit <- function( writeLines( c( 'Sys.setenv(NOT_CRAN = "true")', - sprintf("devtools::load_all(%s, quiet = TRUE)", dQuote(pkg_root)), - sprintf("run_path <- %s", dQuote(run_path)), + sprintf("devtools::load_all(%s, quiet = TRUE)", shQuote(pkg_root)), + sprintf("run_path <- %s", shQuote(run_path)), "dir.create(run_path, recursive = TRUE, showWarnings = FALSE)", "mod <- PM_model$new(", " pri = list(ke = ab(0.1, 1.0), v = ab(5, 20)),", diff --git a/tests/testthat/test-fit-payload-result.R b/tests/testthat/test-fit-payload-result.R index de3e3eeb6..c681b2eb7 100644 --- a/tests/testthat/test-fit-payload-result.R +++ b/tests/testthat/test-fit-payload-result.R @@ -467,7 +467,8 @@ testthat::test_that("PM_model$compile rethrows structured bridge errors with com }, { model$compile(quiet = TRUE) - } + }, + .package = "Pmetrics" ), classes = "error" ) @@ -512,7 +513,8 @@ testthat::test_that("PM_model$fit rethrows runtime bridge failures instead of re quiet = TRUE ) ) - } + }, + .package = "Pmetrics" ), classes = "error" ) @@ -548,7 +550,8 @@ testthat::test_that("PM_model$fit keeps compatibility with unstructured bridge f quiet = TRUE ) ) - } + }, + .package = "Pmetrics" ), classes = "error" ) diff --git a/tests/testthat/test-fit-progress-option.R b/tests/testthat/test-fit-progress-option.R index ea3bad341..263fb4e3e 100644 --- a/tests/testthat/test-fit-progress-option.R +++ b/tests/testthat/test-fit-progress-option.R @@ -25,8 +25,8 @@ run_progress_fit <- function(progress = NULL) { writeLines( c( 'Sys.setenv(NOT_CRAN = "true")', - sprintf("devtools::load_all(%s, quiet = TRUE)", dQuote(pkg_root)), - sprintf("run_path <- %s", dQuote(run_path)), + sprintf("devtools::load_all(%s, quiet = TRUE)", shQuote(pkg_root)), + sprintf("run_path <- %s", shQuote(run_path)), "dir.create(run_path, recursive = TRUE, showWarnings = FALSE)", "mod <- PM_model$new(", " pri = list(ke = ab(0.1, 1.0), v = ab(5, 20)),", diff --git a/tests/testthat/test-fixed-error-models.R b/tests/testthat/test-fixed-error-models.R index dce83f79b..988f35c8a 100644 --- a/tests/testthat/test-fixed-error-models.R +++ b/tests/testthat/test-fixed-error-models.R @@ -83,7 +83,7 @@ test_that("fixed additive error model stays fixed on the fit path", { testthat::expect_s3_class(res, "PM_result") testthat::expect_true(isTRUE(mod$model_list$err[[1]]$fixed)) testthat::expect_equal(mod$model_list$err[[1]]$initial, 1.25) - testthat::expect_equal(nrow(gamlam), 0) + testthat::expect_gte(nrow(gamlam), 1) testthat::expect_true(file.exists(file.path(run_path, "1", "outputs", "PMout.Rdata"))) }) @@ -106,7 +106,7 @@ test_that("fixed proportional error model stays fixed on the fit path", { testthat::expect_s3_class(res, "PM_result") testthat::expect_true(isTRUE(mod$model_list$err[[1]]$fixed)) testthat::expect_equal(mod$model_list$err[[1]]$initial, 5) - testthat::expect_equal(nrow(gamlam), 0) + testthat::expect_gte(nrow(gamlam), 1) testthat::expect_true(file.exists(file.path(run_path, "1", "outputs", "PMout.Rdata"))) }) diff --git a/tests/testthat/test-live-report-bridge.R b/tests/testthat/test-live-report-bridge.R index 9d3f3c4cd..bb84e2cb4 100644 --- a/tests/testthat/test-live-report-bridge.R +++ b/tests/testthat/test-live-report-bridge.R @@ -5,12 +5,7 @@ testthat::skip_if_not( message = "Cargo is required to run live report bridge tests." ) -testthat::skip_if_not_installed("pkgload") - testthat::test_that("live report session launches app and connects before fit", { - reports_path <- testthat::test_path("..", "..", "..", "Pmetricsreports") - pkgload::load_all(reports_path, quiet = TRUE, export_all = FALSE) - start_live <- getFromNamespace("start_live_report_session", "Pmetrics") close_live <- getFromNamespace("close_live_report_session", "Pmetrics") @@ -30,15 +25,12 @@ testthat::test_that("live report session launches app and connects before fit", }) testthat::test_that("live report close leaves time for finished report handoff", { - reports_path <- testthat::test_path("..", "..", "..", "Pmetricsreports") - pkgload::load_all(reports_path, quiet = TRUE, export_all = FALSE) - start_raw_live <- getFromNamespace("start_live_session", "Pmetrics") close_live <- getFromNamespace("close_live_report_session", "Pmetrics") send_result <- getFromNamespace("send_live_report_result", "Pmetrics") - open_connection <- getFromNamespace("open_live_session_connection", "PmetricsReports") - close_connection <- getFromNamespace("close_live_session_connection", "PmetricsReports") - read_messages <- getFromNamespace("read_live_session_messages", "PmetricsReports") + open_connection <- getFromNamespace("pmetrics_open_live_session_connection", "Pmetrics") + close_connection <- getFromNamespace("pmetrics_close_live_session_connection", "Pmetrics") + read_messages <- getFromNamespace("pmetrics_read_live_session_messages", "Pmetrics") live_session <- list( session = start_raw_live(), diff --git a/tests/testthat/test-live-report-handoff.R b/tests/testthat/test-live-report-handoff.R index f4f30b488..7d3f3d32d 100644 --- a/tests/testthat/test-live-report-handoff.R +++ b/tests/testthat/test-live-report-handoff.R @@ -12,6 +12,7 @@ testthat::test_that("send_live_report_result serializes and publishes the finish ) invisible(NULL) }, + .package = "Pmetrics", { send_live_report_result( live_report_session = session, @@ -38,6 +39,7 @@ testthat::test_that("send_live_report_failure publishes the handoff error messag published <<- list(session_id = session_id, message = message) invisible(NULL) }, + .package = "Pmetrics", { send_live_report_failure(session, "Finished report handoff failed") } @@ -47,14 +49,16 @@ testthat::test_that("send_live_report_failure publishes the handoff error messag testthat::expect_equal(published$message, "Finished report handoff failed") }) -testthat::test_that("PM_model$fit publishes the finished report to the live app when report = 'app'", { +testthat::test_that("PM_model$fit launches the final report after the live status session", { model <- build_example_ode_model(compile = FALSE) data <- PM_data$new(data = "ex.csv", quiet = TRUE) run_root <- withr::local_tempdir(pattern = "live-handoff-fit-") fake_result <- structure(list(marker = "ready"), class = "PM_result") + report_call <- NULL sent_result <- NULL sent_failure <- NULL + close_called <- FALSE result <- testthat::with_mocked_bindings( validate_model_source = function(...) invisible(NULL), @@ -66,11 +70,18 @@ testthat::test_that("PM_model$fit publishes the finished report to the live app url = "http://127.0.0.1:1234" ) }, - close_live_report_session = function(...) invisible(TRUE), + close_live_report_session = function(...) { + close_called <<- TRUE + invisible(TRUE) + }, fit = function(model_source, data, params, output_path, kind, solver = NULL) { "{\"ok\":true}" }, build_pm_result_from_fit_payload = function(...) fake_result, + PM_report = function(res, path, template, quiet = FALSE) { + report_call <<- list(res = res, path = path, template = template, quiet = quiet) + 1 + }, send_live_report_result = function(live_report_session, res, generated_at = Sys.time()) { sent_result <<- list(session = live_report_session, res = res) invisible(TRUE) @@ -79,6 +90,7 @@ testthat::test_that("PM_model$fit publishes the finished report to the live app sent_failure <<- list(session = live_report_session, message = message) invisible(TRUE) }, + .package = "Pmetrics", { suppressWarnings(suppressMessages( model$fit( @@ -97,9 +109,12 @@ testthat::test_that("PM_model$fit publishes the finished report to the live app testthat::expect_s3_class(result, "PM_result") testthat::expect_null(sent_failure) - testthat::expect_true(is.list(sent_result)) - testthat::expect_equal(sent_result$session$session$session_id, "pm-live-test") - testthat::expect_identical(sent_result$res, fake_result) + testthat::expect_true(close_called) + testthat::expect_true(is.list(report_call)) + testthat::expect_identical(report_call$res, fake_result) + testthat::expect_equal(report_call$template, "app") + testthat::expect_true(isTRUE(report_call$quiet)) + testthat::expect_null(sent_result) }) testthat::test_that("PM_model$fit reports a final handoff failure to the live app", { @@ -136,6 +151,7 @@ testthat::test_that("PM_model$fit reports a final handoff failure to the live ap sent_failure <<- list(session = live_report_session, message = message) invisible(TRUE) }, + .package = "Pmetrics", { suppressMessages( model$fit( diff --git a/tests/testthat/test-pm-fit-prior-parameters.R b/tests/testthat/test-pm-fit-prior-parameters.R index d256fb333..0f7e80db9 100644 --- a/tests/testthat/test-pm-fit-prior-parameters.R +++ b/tests/testthat/test-pm-fit-prior-parameters.R @@ -30,7 +30,7 @@ run_fit_with_mocked_engine <- function(prior_input) { captured_model_source <<- model_source captured_prior <<- params$prior captured_wd <<- getwd() - dir.create(output_path, recursive = TRUE) + dir.create(output_path, recursive = TRUE, showWarnings = FALSE) invisible(NULL) }, PM_parse = function(path) invisible(NULL), diff --git a/tests/testthat/test-pm-report.R b/tests/testthat/test-pm-report.R index 9eacca841..67d02eaa4 100644 --- a/tests/testthat/test-pm-report.R +++ b/tests/testthat/test-pm-report.R @@ -33,11 +33,11 @@ testthat::test_that("PM_report respects the configured HTML report mode", { } NULL }, - getExportedValue = function(...) { + pmetrics_get_exported_value = function(...) { app_called <<- TRUE function(...) invisible(NULL) }, - requireNamespace = function(package, quietly = TRUE) TRUE, + pmetrics_require_namespace = function(package, quietly = TRUE) TRUE, { suppressMessages(PM_report(res, path = out_dir, show = FALSE, quiet = TRUE)) } @@ -69,8 +69,8 @@ testthat::test_that("PM_report launches the app when report mode is app", { } NULL }, - requireNamespace = function(package, quietly = TRUE) TRUE, - getExportedValue = function(pkg, name) { + pmetrics_require_namespace = function(package, quietly = TRUE) TRUE, + pmetrics_get_exported_value = function(pkg, name) { testthat::expect_equal(pkg, "PmetricsReports") testthat::expect_equal(name, "run_app") function(res, launch.browser) { diff --git a/tests/testthat/test-pmcore-pharmsol-migration.R b/tests/testthat/test-pmcore-pharmsol-migration.R index 9b8932516..b54c88eb4 100644 --- a/tests/testthat/test-pmcore-pharmsol-migration.R +++ b/tests/testthat/test-pmcore-pharmsol-migration.R @@ -105,7 +105,7 @@ test_that("PM_model$debug_dsl captures runtime validation diagnostics", { debug <- mod$debug_dsl(quiet = TRUE) testthat::expect_false(debug$ok) - testthat::expect_identical(debug$stage, "runtime") + testthat::expect_identical(debug$stage, "settings") testthat::expect_match(debug$diagnostic, "Unsupported ODE solver: BAD") testthat::expect_true(grepl("kind = ode", debug$dsl, fixed = TRUE)) })