From 2575d653021c52b02851c43651b4add3a587756b Mon Sep 17 00:00:00 2001 From: Casale Date: Sun, 23 Nov 2025 13:39:11 +0100 Subject: [PATCH 01/44] chore: prepare v1.4 --- CHANGELOG.md | 4 ++++ Cargo.lock | 12 ++++++------ Cargo.toml | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf1f0752..f032efac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## 0.1.4 +*Date: * + + ## 0.1.3 *Date: 11/23/2025* diff --git a/Cargo.lock b/Cargo.lock index 505ac799..763560e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -149,7 +149,7 @@ checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "cmtool" -version = "0.1.3" +version = "0.1.4" dependencies = [ "clap", "cmtool-core", @@ -159,7 +159,7 @@ dependencies = [ [[package]] name = "cmtool-core" -version = "0.1.3" +version = "0.1.4" dependencies = [ "cmtool-data", "enum_dispatch", @@ -169,7 +169,7 @@ dependencies = [ [[package]] name = "cmtool-cxx" -version = "0.1.3" +version = "0.1.4" dependencies = [ "cmtool-data", "cxx", @@ -180,7 +180,7 @@ dependencies = [ [[package]] name = "cmtool-data" -version = "0.1.3" +version = "0.1.4" dependencies = [ "enum_dispatch", "nalgebra", @@ -194,7 +194,7 @@ dependencies = [ [[package]] name = "cmtool-example" -version = "0.1.3" +version = "0.1.4" dependencies = [ "cmtool-data", "nalgebra", @@ -204,7 +204,7 @@ dependencies = [ [[package]] name = "cmtool-python" -version = "0.1.3" +version = "0.1.4" dependencies = [ "cmtool-data", "nalgebra", diff --git a/Cargo.toml b/Cargo.toml index bda936f0..06fccf1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ resolver = "3" metadata.crane.name = "cmtool" [workspace.package] -version = "0.1.3" +version = "0.1.4" edition = "2024" authors = ["Benjamin Casale"] description = "Compartment Modelling Approach tool" From c4fa6e4ae408bffee2db226d789b7bf4e9895ac2 Mon Sep 17 00:00:00 2001 From: Casale Date: Sun, 23 Nov 2025 15:55:48 +0100 Subject: [PATCH 02/44] feat: cmtool-assemble for xml reactor descriptor parsing --- Cargo.lock | 674 +++++++++++++++++- Cargo.toml | 8 +- cmtool-assemble/Cargo.toml | 23 + cmtool-assemble/build.rs | 60 ++ cmtool-assemble/datamodel/connections.xsd | 43 ++ cmtool-assemble/datamodel/main.xsd | 21 + cmtool-assemble/datamodel/reactors.xsd | 102 +++ cmtool-assemble/datamodel/units.xsd | 93 +++ cmtool-assemble/src/data.rs | 35 + {cmtool => cmtool-assemble}/src/generators.rs | 208 +++--- cmtool-assemble/src/lib.rs | 45 ++ cmtool-assemble/src/map_generation.rs | 149 ++++ .../src/parser/generated_domain.rs | 1 + cmtool-assemble/src/parser/mod.rs | 39 + cmtool-assemble/src/parser/reactors.rs | 188 +++++ cmtool/Cargo.toml | 1 + cmtool/src/args/mod.rs | 61 +- cmtool/src/lib.rs | 18 +- cmtool/src/main.rs | 29 +- {example => examples}/Cargo.toml | 22 +- examples/README.md | 5 + examples/data/case_0d1d/reactors.xml | 72 ++ examples/data/case_0d1d_liq/reactors.xml | 50 ++ examples/data/neubauer/reactors.xml | 47 ++ examples/data/neubauer/reactors_batch.xml | 45 ++ examples/data/simple_0d/reactors.xml | 17 + examples/data/simple_0d1d/reactors.xml | 48 ++ examples/data/simple_1d/reactors.xml | 28 + {example => examples}/python/mixing_simple.py | 0 examples/src/lib.rs | 4 + examples/src/map_generation/case_0d1d.rs | 7 + examples/src/map_generation/mod.rs | 45 ++ examples/src/map_generation/neubauer.rs | 10 + examples/src/map_generation/simple_0d.rs | 7 + examples/src/map_generation/simple_1d.rs | 7 + {example => examples}/src/mixing_simple.rs | 0 36 files changed, 2079 insertions(+), 133 deletions(-) create mode 100644 cmtool-assemble/Cargo.toml create mode 100644 cmtool-assemble/build.rs create mode 100644 cmtool-assemble/datamodel/connections.xsd create mode 100644 cmtool-assemble/datamodel/main.xsd create mode 100644 cmtool-assemble/datamodel/reactors.xsd create mode 100644 cmtool-assemble/datamodel/units.xsd create mode 100644 cmtool-assemble/src/data.rs rename {cmtool => cmtool-assemble}/src/generators.rs (79%) create mode 100644 cmtool-assemble/src/lib.rs create mode 100644 cmtool-assemble/src/map_generation.rs create mode 100644 cmtool-assemble/src/parser/generated_domain.rs create mode 100644 cmtool-assemble/src/parser/mod.rs create mode 100644 cmtool-assemble/src/parser/reactors.rs rename {example => examples}/Cargo.toml (54%) create mode 100644 examples/README.md create mode 100644 examples/data/case_0d1d/reactors.xml create mode 100644 examples/data/case_0d1d_liq/reactors.xml create mode 100644 examples/data/neubauer/reactors.xml create mode 100644 examples/data/neubauer/reactors_batch.xml create mode 100644 examples/data/simple_0d/reactors.xml create mode 100644 examples/data/simple_0d1d/reactors.xml create mode 100644 examples/data/simple_1d/reactors.xml rename {example => examples}/python/mixing_simple.py (100%) create mode 100644 examples/src/lib.rs create mode 100644 examples/src/map_generation/case_0d1d.rs create mode 100644 examples/src/map_generation/mod.rs create mode 100644 examples/src/map_generation/neubauer.rs create mode 100644 examples/src/map_generation/simple_0d.rs create mode 100644 examples/src/map_generation/simple_1d.rs rename {example => examples}/src/mixing_simple.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 763560e2..7dd6f803 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,31 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + [[package]] name = "adler2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + [[package]] name = "anstream" version = "0.6.20" @@ -58,6 +77,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + [[package]] name = "approx" version = "0.5.1" @@ -79,6 +104,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + [[package]] name = "bytemuck" version = "1.23.2" @@ -91,6 +122,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + [[package]] name = "cc" version = "1.2.37" @@ -152,11 +189,26 @@ name = "cmtool" version = "0.1.4" dependencies = [ "clap", + "cmtool-assemble", "cmtool-core", "cmtool-data", "thiserror", ] +[[package]] +name = "cmtool-assemble" +version = "0.1.4" +dependencies = [ + "cmtool-data", + "ndarray", + "serde 1.0.225", + "serde-xml-rs", + "serde_json", + "serde_xml", + "thiserror", + "xsd-parser", +] + [[package]] name = "cmtool-core" version = "0.1.4" @@ -186,7 +238,7 @@ dependencies = [ "nalgebra", "nalgebra-sparse", "ndarray", - "serde", + "serde 1.0.225", "serde_cbor", "serde_json", "thiserror", @@ -196,6 +248,7 @@ dependencies = [ name = "cmtool-example" version = "0.1.4" dependencies = [ + "cmtool-assemble", "cmtool-data", "nalgebra", "nalgebra-sparse", @@ -218,7 +271,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" dependencies = [ - "serde", + "serde 1.0.225", "termcolor", "unicode-width", ] @@ -300,6 +353,26 @@ dependencies = [ "syn", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "enum_dispatch" version = "0.3.13" @@ -340,6 +413,104 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "glam" version = "0.14.0" @@ -454,6 +625,108 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "indexmap" version = "2.12.0" @@ -482,6 +755,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.175" @@ -497,6 +776,30 @@ dependencies = [ "cc", ] +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +dependencies = [ + "log 0.4.28", +] + [[package]] name = "log" version = "0.4.28" @@ -721,12 +1024,53 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.32" @@ -748,6 +1092,15 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + [[package]] name = "proc-macro2" version = "1.0.101" @@ -827,7 +1180,19 @@ source = "git+https://github.com/elrnv/quick-xml.git?branch=binary-support4#d66f dependencies = [ "memchr", "ref-cast", - "serde", + "serde 1.0.225", +] + +[[package]] +name = "quick-xml" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +dependencies = [ + "encoding_rs", + "memchr", + "serde 1.0.225", + "tokio", ] [[package]] @@ -845,6 +1210,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + [[package]] name = "ref-cast" version = "1.0.24" @@ -865,6 +1239,35 @@ dependencies = [ "syn", ] +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + [[package]] name = "rustc-hash" version = "2.1.1" @@ -886,12 +1289,24 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "scratch" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2" +[[package]] +name = "serde" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" + [[package]] name = "serde" version = "1.0.225" @@ -902,6 +1317,18 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-xml-rs" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2215ce3e6a77550b80a1c37251b7d294febaf42e36e21b7b411e0bf54d540d" +dependencies = [ + "log 0.4.28", + "serde 1.0.225", + "thiserror", + "xml", +] + [[package]] name = "serde_cbor" version = "0.11.2" @@ -909,7 +1336,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" dependencies = [ "half", - "serde", + "serde 1.0.225", ] [[package]] @@ -941,10 +1368,20 @@ dependencies = [ "itoa", "memchr", "ryu", - "serde", + "serde 1.0.225", "serde_core", ] +[[package]] +name = "serde_xml" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56346e526b0828da6b7a6a867076a6ae22d188ffd7a5511b4ffbd815def0ba95" +dependencies = [ + "log 0.3.9", + "serde 0.8.23", +] + [[package]] name = "shlex" version = "1.3.0" @@ -964,6 +1401,24 @@ dependencies = [ "wide", ] +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + [[package]] name = "strsim" version = "0.11.1" @@ -981,6 +1436,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "target-lexicon" version = "0.13.3" @@ -1016,6 +1482,57 @@ dependencies = [ "syn", ] +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "bytes", + "pin-project-lite", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + [[package]] name = "trim-in-place" version = "0.1.7" @@ -1052,6 +1569,24 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde 1.0.225", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -1067,13 +1602,13 @@ dependencies = [ "bytemuck", "byteorder", "flate2", - "log", + "log 0.4.28", "lz4_flex", "nom", "num-derive", "num-traits", - "quick-xml", - "serde", + "quick-xml 0.36.0", + "serde 1.0.225", "trim-in-place", "xz2", ] @@ -1103,6 +1638,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" version = "0.60.2" @@ -1118,7 +1659,7 @@ version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ - "windows-link", + "windows-link 0.1.3", "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", @@ -1177,6 +1718,44 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "xml" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "838dd679b10a4180431ce7c2caa6e5585a7c8f63154c19ae99345126572e80cc" + +[[package]] +name = "xsd-parser" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e63d98ea458b38a90a586775ba5bfeb3408eb0b2816f3cc457a8dbad79609fca" +dependencies = [ + "Inflector", + "anyhow", + "base64", + "bitflags", + "encoding_rs", + "futures", + "indexmap", + "parking_lot", + "proc-macro2", + "quick-xml 0.38.4", + "quote", + "regex", + "serde 1.0.225", + "smallvec", + "thiserror", + "tracing", + "unindent", + "url", +] + [[package]] name = "xz2" version = "0.1.7" @@ -1185,3 +1764,80 @@ checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" dependencies = [ "lzma-sys", ] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 06fccf1e..005d477c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["cmtool-data", "cmtool-core", "cmtool", "cmtool-python","cmtool-cxx" ,"example"] +members = ["cmtool-data", "cmtool-core", "cmtool", "cmtool-python","cmtool-cxx" ,'cmtool-assemble',"examples"] resolver = "3" metadata.crane.name = "cmtool" @@ -27,13 +27,17 @@ overflow-checks = false [workspace.dependencies] cmtool-core = { path = "./cmtool-core" } cmtool-data = { path = "./cmtool-data" } +cmtool-assemble = { path = "./cmtool-assemble" } + serde = { version = "1.0.219", features = ["derive"] } serde_cbor = "0.11.2" serde_json = "1.0.140" +serde-xml-rs = "0.8.1" +serde_xml = "0.9.1" thiserror = "2.0.12" enum_dispatch = "0.3.13" clap = { version = "4.5.41", features = ["derive"] } - +xsd-parser = "1.3.0" # nalgebra = "0.34.0" nalgebra-sparse = "0.11.0" diff --git a/cmtool-assemble/Cargo.toml b/cmtool-assemble/Cargo.toml new file mode 100644 index 00000000..959050a3 --- /dev/null +++ b/cmtool-assemble/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "cmtool-assemble" +edition.workspace = true +version.workspace = true +authors.workspace = true +description.workspace = true +documentation.workspace = true + +[lib] +name = "cmtool_assemble" +path = "src/lib.rs" + +[build-dependencies] +xsd-parser.workspace=true + +[dependencies] +ndarray.workspace = true +cmtool-data.workspace = true +serde_xml.workspace = true +serde-xml-rs.workspace = true +serde.workspace = true +thiserror.workspace = true +serde_json.workspace = true diff --git a/cmtool-assemble/build.rs b/cmtool-assemble/build.rs new file mode 100644 index 00000000..340072f9 --- /dev/null +++ b/cmtool-assemble/build.rs @@ -0,0 +1,60 @@ +use crate::fs::File; +use std::fs; +use std::io::Write; +use xsd_parser::{ + Config, Error, + config::{GeneratorFlags, InterpreterFlags, OptimizerFlags, RenderStep, Schema}, + generate, +}; + +static ROOT: &str = "./datamodel"; + +fn domain_schema() -> Result<(), Error> { + let files = [ + format!("{}/units.xsd", ROOT), + format!("{}/reactors.xsd", ROOT), + format!("{}/connections.xsd", ROOT), + format!("{}/main.xsd", ROOT), + ]; + + let mut cfg = Config::default(); + cfg.parser.schemas = files + .into_iter() + .map(|f| { + println!("cargo:rerun-if-changed={}", f); + Schema::File(f.into()) + }) + .collect(); + cfg = cfg.with_render_steps([ + //RenderStep::Types, + RenderStep::Defaults, + RenderStep::TypesSerdeXmlRs { + version: xsd_parser::config::SerdeXmlRsVersion::Version08AndAbove, + }, + // RenderStep::NamespaceConstants, + + // RenderStep::QuickXmlDeserialize { + // boxed_deserializer: false, + // }, + // RenderStep::TypesSerdeQuickXml, + ]); + + cfg = cfg.with_derive(["Debug", "Clone"]); + cfg.interpreter.flags = InterpreterFlags::all() + - InterpreterFlags::WITH_NUM_BIG_INT + - InterpreterFlags::WITH_XS_ANY_TYPE; + cfg.optimizer.flags = OptimizerFlags::all(); + cfg.generator.flags.insert(GeneratorFlags::all()); + let code = generate(cfg).expect("Failed to generate Rust code from XSD"); + let mut file = File::create("src/parser/generated_domain.rs")?; + file.write_all(code.to_string().as_bytes())?; + Ok(()) +} + + + +fn main() -> Result<(), Error> { + domain_schema()?; + println!("cargo:rerun-if-changed=cmtool-assemble/build.rs"); + Ok(()) +} diff --git a/cmtool-assemble/datamodel/connections.xsd b/cmtool-assemble/datamodel/connections.xsd new file mode 100644 index 00000000..553fe73e --- /dev/null +++ b/cmtool-assemble/datamodel/connections.xsd @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cmtool-assemble/datamodel/main.xsd b/cmtool-assemble/datamodel/main.xsd new file mode 100644 index 00000000..0abe838e --- /dev/null +++ b/cmtool-assemble/datamodel/main.xsd @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + diff --git a/cmtool-assemble/datamodel/reactors.xsd b/cmtool-assemble/datamodel/reactors.xsd new file mode 100644 index 00000000..8e8280cf --- /dev/null +++ b/cmtool-assemble/datamodel/reactors.xsd @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cmtool-assemble/datamodel/units.xsd b/cmtool-assemble/datamodel/units.xsd new file mode 100644 index 00000000..5d2505a5 --- /dev/null +++ b/cmtool-assemble/datamodel/units.xsd @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cmtool-assemble/src/data.rs b/cmtool-assemble/src/data.rs new file mode 100644 index 00000000..837079bc --- /dev/null +++ b/cmtool-assemble/src/data.rs @@ -0,0 +1,35 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +#[derive(Default, Clone, Serialize, Deserialize)] +pub struct DomainInfo { + pub compartment_cumsum: HashMap, + pub total_number_compartment: usize, + pub pfr_names: Vec, +} + +#[derive(Clone, Serialize, Deserialize, Default)] +pub struct DomainData { + pub connections: Option<[cmtool_data::RawDataFlux; 2]>, + pub info: DomainInfo, +} + + + +impl DomainInfo { + pub fn get_relative_compartment_number( + &self, + reactor_id: &str, + relative_index: usize, + ) -> Option { + self.compartment_cumsum + .get(reactor_id) + .map(|cum_sum| relative_index + *cum_sum) + } +} + +#[derive(Debug, PartialEq)] +pub enum FlowDirection { + In, + Out, +} diff --git a/cmtool/src/generators.rs b/cmtool-assemble/src/generators.rs similarity index 79% rename from cmtool/src/generators.rs rename to cmtool-assemble/src/generators.rs index d0abfab9..98aa3a86 100644 --- a/cmtool/src/generators.rs +++ b/cmtool-assemble/src/generators.rs @@ -1,11 +1,9 @@ -use cmtool_data::CMCase; -use cmtool_data::CMCaseReader; -use cmtool_data::CMCaseWriter; -use cmtool_data::CMExportType; -use cmtool_data::PhaseCM; -use cmtool_data::RawData; -use cmtool_data::RawPhase; -use cmtool_data::{CMAExportType, CMCaseJson, RawDataFlux, RawDataScalar, RawFlux}; +use crate::CMError; + +use cmtool_data::{ + CMAExportType, CMCase, CMCaseJson, CMCaseReader, CMCaseWriter, CMExportType, PhaseCM, RawData, + RawDataFlux, RawDataScalar, RawFlux, RawPhase, +}; use std::path::Path; const LIQUID_PAIR: (CMAExportType, CMAExportType) = @@ -17,17 +15,14 @@ type PairType = (CMAExportType, CMAExportType); const PAIRS: (PairType, PairType) = (LIQUID_PAIR, GAS_PAIR); -use thiserror::Error; -#[derive(Error, Debug)] -pub enum CmtoolError { - #[error("Cmtool: {0}")] - Data(#[from] cmtool_data::DataError), - - #[error("Cmtool: {0}")] - Core(#[from] cmtool_core::CoreError), - - #[error("Cmtool: {0}")] - Custom(String), +pub struct PFRDescription { + pub n_compartment: usize, + pub length: f64, + pub diameter: f64, + pub liquid_flow: f64, + pub gas_flow: f64, + pub gas_fraction: f64, + pub axial_dispersion: f64, } pub struct Generator { @@ -39,8 +34,41 @@ struct Field0D { value: f64, } +fn resolve_path( + case: &CMCase, + relative_path: &str, + value: CMAExportType, +) -> Result { + case.resolve(relative_path, value) + .ok_or(CMError::Custom("Error resolving path".to_string())) +} + +fn get_raw_phase( + flows: Vec, + vol: Vec, + phase: PhaseCM, +) -> Vec { + flows + .into_iter() + .zip(vol) + .map(|(f, v)| RawPhase { + flow: f, + volume: v, + identifier: phase, + }) + .collect() +} + +fn filter_phase(raw_phase: &[RawPhase], phase: PhaseCM) -> Vec { + raw_phase + .iter() + .filter(|p| p.identifier == phase) + .cloned() + .collect() +} + impl Generator { - fn f_write(f: impl RawData, r: &str, p: &str) -> Result<(), CmtoolError> { + fn f_write(f: impl RawData, r: &str, p: &str) -> Result<(), CMError> { f.write_raw(&format!("{}/{}", r, p))?; Ok(()) } @@ -56,7 +84,7 @@ impl Generator { case: &mut CMCase, phase: RawPhase, relative_path: Option, - ) -> Result<(), CmtoolError> { + ) -> Result<(), CMError> { let (flowp, volumep) = phase.write(dest)?; let flowp = match &relative_path { Some(rel) => format!("./{}/{}", rel, flowp), @@ -79,7 +107,7 @@ impl Generator { volume: f64, phase: PhaseCM, dest: Option, - ) -> Result<(), CmtoolError> { + ) -> Result<(), CMError> { let mut phase = cmtool_data::RawPhase::new(1, 1, phase); phase.flow.fluxes[0] = Default::default(); //Not usefull because new already makes default phase.volume.values.push(volume.into()); @@ -99,7 +127,7 @@ impl Generator { gas_volume: f64, fields: Option<&[Field0D]>, dest: Option, - ) -> Result { + ) -> Result { let mut case = CMCase::default(); case.n_div = [1, 0, 0]; @@ -121,7 +149,7 @@ impl Generator { total_volume: f64, gas_fraction: f64, dest: Option, - ) -> Result { + ) -> Result { if !(0. ..=1.).contains(&gas_fraction) { panic!("TODO: handle error gas fraction generation 0d"); } @@ -132,22 +160,23 @@ impl Generator { self.generate_0d(liquid_volume, gas_volume, None, dest) } + #[allow(clippy::too_many_arguments)] fn generate_1d( &mut self, case: &mut CMCase, n_compartment: usize, length: f64, diameter: f64, - liquid_flow: f64, + flow: f64, volume_fraction: f64, axial_dispersion: f64, gas: bool, dest: Option, - ) -> Result<(), CmtoolError> { + ) -> Result<(), CMError> { let dx = length / (n_compartment as f64); let reactor_section_area = std::f64::consts::PI * diameter.powf(2.) / 4.; let compartment_volume = volume_fraction * dx * reactor_section_area; - let flow_velocity = liquid_flow / reactor_section_area; + let flow_velocity = flow / reactor_section_area; let n_flow = n_compartment - 1; let flow_source_target = reactor_section_area / dx * (flow_velocity + axial_dispersion); @@ -184,15 +213,18 @@ impl Generator { pub fn generate_1d_from_fraction( &mut self, - n_compartment: usize, - length: f64, - diameter: f64, - liquid_flow: f64, - gas_flow: f64, - gas_fraction: f64, - axial_dispersion: f64, + + PFRDescription { + n_compartment, + length, + diameter, + liquid_flow, + gas_flow, + gas_fraction, + axial_dispersion, + }: PFRDescription, dest: Option, - ) -> Result { + ) -> Result { let mut case = CMCase::default(); case.n_div = [0, 0, n_compartment as u32]; @@ -228,13 +260,12 @@ impl Generator { Ok(case) } - //TODO add result type fn merge_phase( // flows: Vec, // volumes: Vec phases: Vec, connections: Option, - ) -> Result { + ) -> Result { let mut phase = RawPhase::new(0, 0, phases[0].identifier); // let mut merge_phase_flow = RawDataFlux::new(0, 0); // @@ -279,23 +310,14 @@ impl Generator { &self, dest: &str, connections: Option<[RawDataFlux; 2]>, - ) -> Result<(), CmtoolError> { - let liquid_phase = self - .raw_phase - .iter() - .filter(|p| p.identifier == PhaseCM::Liquid) - .cloned() - .collect(); - - let gasphase: Vec<_> = self - .raw_phase - .iter() - .filter(|p| p.identifier == PhaseCM::Gas) - .cloned() - .collect(); + ) -> Result<(), CMError> { + + let liquid_phase = filter_phase(&self.raw_phase,PhaseCM::Liquid); + let gasphase = filter_phase(&self.raw_phase,PhaseCM::Gas); + let path = format!("{}/merged", dest); - std::fs::create_dir_all(&path).unwrap(); //FIXME + std::fs::create_dir_all(&path)?; //FIXME let mut case = CMCase::default(); // case.n_div = n_div; let relative = Some(String::from("merged")); //TODO Clean this @@ -321,7 +343,7 @@ impl Generator { dest: &str, ids: &[String], connections: Option<[RawDataFlux; 2]>, - ) -> Result<(), CmtoolError> { + ) -> Result<(), CMError> { let mut liquid_flows = Vec::with_capacity(ids.len()); let mut liquid_volumes = Vec::with_capacity(ids.len()); let mut gas_flows = Vec::with_capacity(ids.len()); @@ -332,11 +354,10 @@ impl Generator { let case = CMCaseJson::read_case(Path::new(&format!("{}/{}/cma_case", dest, id)))?; let relative_path = format!("{}/{}", dest, id); let (liquid_flow, liquid_volume) = PAIRS.0; - let path = case - .resolve(&relative_path, liquid_flow) - .ok_or(CmtoolError::Custom("Error resolve".to_string()))?; - let rf = RawDataFlux::read_raw(path) - .ok_or(CmtoolError::Custom("Error reading".to_string()))?; + + let path = resolve_path(&case, &relative_path, liquid_flow)?; + let rf = + RawDataFlux::read_raw(path).ok_or(CMError::Custom("Error reading".to_string()))?; let n_zone = rf.header.n_zone as usize; liquid_flows.push(rf); @@ -344,16 +365,14 @@ impl Generator { n_div[1] += case.n_div[1]; n_div[2] += case.n_div[2]; - let path = case - .resolve(&relative_path, liquid_volume) - .ok_or(CmtoolError::Custom("Error resolve".to_string()))?; + let path = resolve_path(&case, &relative_path, liquid_volume)?; + let sc = RawDataScalar::read_raw(path) - .ok_or(CmtoolError::Custom("Error reading".to_string()))?; + .ok_or(CMError::Custom("Error reading".to_string()))?; liquid_volumes.push(sc); let (gas_flow, gas_volume) = PAIRS.1; - let path = case - .resolve(&relative_path, gas_flow) - .ok_or(CmtoolError::Custom("Error resolve".to_string()))?; + let path = resolve_path(&case, &relative_path, gas_flow)?; + let rf = match RawDataFlux::read_raw(path) { Some(rf) => rf, None => { @@ -363,9 +382,7 @@ impl Generator { }; gas_flows.push(rf); - let path = case - .resolve(&relative_path, gas_volume) - .ok_or(CmtoolError::Custom("Error resolve".to_string()))?; + let path = resolve_path(&case, &relative_path, gas_volume)?; let rs = match RawDataScalar::read_raw(path) { Some(rs) => rs, None => { @@ -378,29 +395,12 @@ impl Generator { } let path = format!("{}/merged", dest); - std::fs::create_dir_all(&path).unwrap(); //FIXME + std::fs::create_dir_all(&path)?; let mut case = CMCase::default(); case.n_div = n_div; - let liquid_phases = liquid_flows - .into_iter() - .zip(liquid_volumes) - .map(|(f, v)| RawPhase { - flow: f, - volume: v, - identifier: PhaseCM::Liquid, - }) - .collect(); - - let gas_phases: Vec<_> = gas_flows - .into_iter() - .zip(gas_volumes) - .map(|(f, v)| RawPhase { - flow: f, - volume: v, - identifier: PhaseCM::Gas, - }) - .collect(); + let liquid_phases = get_raw_phase(liquid_flows, liquid_volumes, PhaseCM::Liquid); + let gas_phases = get_raw_phase(gas_flows, gas_volumes, PhaseCM::Gas); let liquid_connection = connections.as_ref().map(|c| c[0].clone()); let gas_connection = connections.as_ref().map(|c| c[1].clone()); @@ -458,12 +458,23 @@ mod tests { let l = 1.; let d = 0.2; let alpha_g = 0.1; + + let desc = PFRDescription { + n_compartment: 10, + length: l, + diameter: d, + liquid_flow: 0.01, + gas_flow: 0.01, + gas_fraction: alpha_g, + axial_dispersion: 1e-9, + }; + let case = Generator::new() - .generate_1d_from_fraction(10, l, d, 0.01, 0.01, alpha_g, 1e-9, Some(path.to_owned())) + .generate_1d_from_fraction(desc, Some(path.to_owned())) .expect("case"); - let liquid_volume_path: String = case - .resolve(path, cmtool_data::CMAExportType::LiquidVolume) - .expect("path"); + + let liquid_volume_path = + resolve_path(&case, path, cmtool_data::CMAExportType::LiquidVolume).unwrap(); let liquid_volume: f64 = cmtool_data::RawDataScalar::read_raw(liquid_volume_path.clone()) .expect("Liquid error") @@ -485,8 +496,19 @@ mod tests { let l = 1.; let d = 0.2; let alpha_g = 0.1; + + let desc = PFRDescription { + n_compartment: 10, + length: l, + diameter: d, + liquid_flow: 0.01, + gas_flow: 0.001, + gas_fraction: alpha_g, + axial_dispersion: 1e-9, + }; + let case = Generator::new() - .generate_1d_from_fraction(10, l, d, 0.01, 0.001, alpha_g, 1e-9, Some(path.to_owned())) + .generate_1d_from_fraction(desc, Some(path.to_owned())) .expect("case"); let liquid_volume_path = case .resolve(path, cmtool_data::CMAExportType::LiquidVolume) diff --git a/cmtool-assemble/src/lib.rs b/cmtool-assemble/src/lib.rs new file mode 100644 index 00000000..14467471 --- /dev/null +++ b/cmtool-assemble/src/lib.rs @@ -0,0 +1,45 @@ +mod data; +mod generators; +mod map_generation; +mod parser; + +use std::io; + +pub use crate::data::{DomainData, DomainInfo}; +use crate::parser::{get_root, parse_domain}; +use map_generation::generate_flowmap; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum CMError { + #[error("Cmtool encountered an unknown error. Please check the input and try again.")] + Default, + + #[error("Cmtool error: {0}")] + Custom(String), + + #[error( + "Cmtool: Mass balance error in PFR '{0}' with phase {1}. Check your inputs or calculations." + )] + MassBalance(String, String), + + #[error("Cmtool data error: {0}. Ensure your data files are correct and accessible.")] + Data(#[from] cmtool_data::DataError), + + #[error("I/O error: Failed to handle the file '{0}'. Check file path and permissions.")] + IO(#[from] io::Error), + + #[error( + "Cmtool parse error: {0}. Verify that the XML input is well-formed and matches expected schema." + )] + Parse(#[from] serde_xml_rs::Error), +} + +pub fn generate_domain(root_dir: &str, reactor_content: &str) -> Result { + let root = get_root(reactor_content)?; + let domain = parse_domain(&root)?; + let root_dir = format!("{}/{}", root_dir, root.run_id); + std::fs::create_dir_all(root_dir.clone())?; + generate_flowmap(&root_dir, &domain, &root.reactors)?; + Ok(domain) +} diff --git a/cmtool-assemble/src/map_generation.rs b/cmtool-assemble/src/map_generation.rs new file mode 100644 index 00000000..be40f63c --- /dev/null +++ b/cmtool-assemble/src/map_generation.rs @@ -0,0 +1,149 @@ +use crate::CMError; +use crate::data::DomainData; +use crate::generators::{Generator, PFRDescription}; +use crate::parser::generated_domain::{self, Reactor0DType}; +use cmtool_data::CMCaseReader; +use cmtool_data::{CCMCaseInfo, CMCaseWriter, DataError}; + +fn get_volume(size: &generated_domain::GeneralSizeType) -> f64 { + match &size { + generated_domain::GeneralSizeType::Volume(v) => v.content as f64, + generated_domain::GeneralSizeType::Dimension(dim) => { + (dim.length.content as f64) + * (dim.diameter.content.powf(2.) as f64) + * std::f64::consts::PI + / 4. + } + } +} + +fn _generate_reactor_0d( + save_intermediate: bool, + generator: &mut Generator, + root: &str, + ids: &mut Vec, + reactor0d: &Reactor0DType, +) -> Result<(), CMError> { + ids.push(reactor0d.id.clone()); + let volume = get_volume(&reactor0d.size); + let path = format!("{}/{}", root, reactor0d.id); + + let mut opt_path = None; + if save_intermediate { + std::fs::create_dir_all(path.clone()).map_err(DataError::IO)?; + + opt_path = Some(path.clone()); + } + + let case = generator.generate_0d_from_fraction( + volume, + reactor0d.volume_fraction.content as f64, + opt_path, + )?; + + if save_intermediate { + T::write_case(case, std::path::Path::new(&format!("{}/cma_case", path)))?; + } + + Ok(()) +} + +fn _generate_reactor_1d( + save_intermediate: bool, + generator: &mut Generator, + root: &str, + ids: &mut Vec, + current_pfr: &generated_domain::Reactor1DType, +) -> Result<(), CMError> { + ids.push(current_pfr.id.clone()); + let path = format!("{}/{}", root, current_pfr.id); + + let mut opt_path = None; + if save_intermediate { + std::fs::create_dir_all(path.clone()).map_err(DataError::IO)?; + opt_path = Some(path.clone()); + } + + match ¤t_pfr.size { + generated_domain::GeneralSizeType::Volume(_) => { + unimplemented!("pfr needs length") + } + generated_domain::GeneralSizeType::Dimension(dim) => { + eprintln!("TODO: PFR GENERATION W/O FLOW RATES"); + + let desc = PFRDescription { + n_compartment: current_pfr.compartments, + length: dim.length.content.into(), + diameter: dim.diameter.content.into(), + liquid_flow: 1.0, + gas_flow: 0.0, + gas_fraction: current_pfr.volume_fraction.content as f64, + axial_dispersion: 1e-9, + }; + + let case: cmtool_data::CMCase = generator.generate_1d_from_fraction(desc, opt_path)?; + if save_intermediate { + T::write_case(case, std::path::Path::new(&format!("{}/cma_case", path)))?; + } + } + }; + Ok(()) +} + +fn generate_partial_flowmap( + save_intermediate: bool, + generator: &mut Generator, + root: &str, + reactors: &generated_domain::ReactorsType, +) -> Result, CMError> { + let mut ids = Vec::with_capacity(reactors.content.len()); + + for reactor in &reactors.content { + match reactor { + generated_domain::ReactorsTypeContent::Reactor0D(r) => { + _generate_reactor_0d::(save_intermediate, generator, root, &mut ids, r)?; + } + generated_domain::ReactorsTypeContent::Reactor1D(r) => { + _generate_reactor_1d::(save_intermediate, generator, root, &mut ids, r)?; + } + generated_domain::ReactorsTypeContent::Reactor3D(reactor3_dtype) => { + todo!("{:?}", reactor3_dtype) + } + } + } + Ok(ids) +} + +pub fn generate_flowmap( + root: &str, + domain: &DomainData, + reactors: &generated_domain::ReactorsType, +) -> Result<(), CMError> { + let mut generator = Generator::new(); + let mut save_intermediate = false; + if reactors.content.len() == 1 { + save_intermediate = true; + } + + let _ids = + generate_partial_flowmap::(save_intermediate, &mut generator, root, reactors)?; + + if _ids.len() > 1 { + if save_intermediate { + generator.merge(root, &_ids, domain.connections.clone())?; + } else { + generator.merge_from_memory(root, domain.connections.clone())?; + } + } else if _ids.len() == 1 && save_intermediate { + let case_path = format!("{}/{}/cma_case", root, _ids[0]); + let prep = format!("./{}", _ids[0]); + let case = CCMCaseInfo::read_case(std::path::Path::new(&case_path))?.prepend_path(&prep); + + CCMCaseInfo::write_case(case, std::path::Path::new(&format!("{}/cma_case", root)))?; + } + + Ok(()) +} + +// fn parse_generate() +// {} diff --git a/cmtool-assemble/src/parser/generated_domain.rs b/cmtool-assemble/src/parser/generated_domain.rs new file mode 100644 index 00000000..28f6605c --- /dev/null +++ b/cmtool-assemble/src/parser/generated_domain.rs @@ -0,0 +1 @@ +use serde :: { Deserialize , Serialize } ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct BaseReactorType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct ConnectionsType { # [serde (default , rename = "Flux")] pub flux : Vec < FluxType > , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct DimensionType { # [serde (default , rename = "@unit")] pub unit : Option < :: std :: string :: String > , # [serde (rename = "#text")] pub content : :: core :: primitive :: f32 , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct FeedFluxType { # [serde (rename = "@phase")] pub phase : :: std :: string :: String , # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "Source")] pub source : NodeType , # [serde (rename = "Target")] pub target : NodeType , # [serde (rename = "Value")] pub value : DimensionType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct FeedsType { # [serde (default , rename = "Flux")] pub flux : Vec < FeedFluxType > , } pub type FlowType = DimensionType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct FluxType { # [serde (rename = "@phase")] pub phase : :: std :: string :: String , # [serde (rename = "Source")] pub source : NodeType , # [serde (rename = "Target")] pub target : NodeType , # [serde (rename = "Value")] pub value : DimensionType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub enum GeneralSizeType { # [serde (rename = "Volume")] Volume (DimensionType) , # [serde (rename = "Dimension")] Dimension (SizeCylindricalType) , } pub type LengthType = DimensionType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct NodeType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "@compartment_id")] pub compartment_id : :: core :: primitive :: usize , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct Reactor0DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct Reactor1DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , # [serde (rename = "Compartments")] pub compartments : :: core :: primitive :: usize , # [serde (rename = "Dispersion")] pub dispersion : DimensionType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct Reactor3DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , # [serde (rename = "file")] pub file : :: std :: string :: String , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct ReactorsType { # [serde (rename = "#content")] pub content : Vec < ReactorsTypeContent > , } # [derive (Debug , Clone , Serialize , Deserialize)] pub enum ReactorsTypeContent { # [serde (rename = "Reactor0D")] Reactor0D (Reactor0DType) , # [serde (rename = "Reactor1D")] Reactor1D (Reactor1DType) , # [serde (rename = "Reactor3D")] Reactor3D (Reactor3DType) , } pub type Root = RootElementType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct RootElementType { # [serde (rename = "@run_id")] pub run_id : :: std :: string :: String , # [serde (rename = "@version")] pub version : :: core :: primitive :: i32 , # [serde (rename = "Reactors")] pub reactors : ReactorsType , # [serde (default , rename = "Connections")] pub connections : Option < ConnectionsType > , # [serde (default , rename = "Feeds")] pub feeds : Option < FeedsType > , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct SizeCylindricalType { # [serde (rename = "Diameter")] pub diameter : DimensionType , # [serde (rename = "Length")] pub length : DimensionType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub enum SizeSpecificationType { # [serde (rename = "Volume")] Volume (DimensionType) , # [serde (rename = "CylindricalDimensions")] CylindricalDimensions (SizeSpecificationCylindricalDimensionsElementType) , } pub type TimeType = DimensionType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct VolumeFractionType { # [serde (default , rename = "@phase")] pub phase : Option < :: std :: string :: String > , # [serde (rename = "#text")] pub content : :: core :: primitive :: f32 , } pub type VolumeType = DimensionType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct SizeSpecificationCylindricalDimensionsElementType { # [serde (rename = "Diameter")] pub diameter : DimensionType , # [serde (rename = "Length")] pub length : DimensionType , } pub mod xs { use serde :: { Deserialize , Serialize } ; # [derive (Debug , Clone , Serialize , Deserialize , Default)] pub struct EntitiesType (pub Vec < :: std :: string :: String >) ; pub type EntityType = EntitiesType ; pub type IdType = :: std :: string :: String ; pub type IdrefType = :: std :: string :: String ; pub type IdrefsType = EntitiesType ; pub type NcNameType = :: std :: string :: String ; pub type NmtokenType = :: std :: string :: String ; pub type NmtokensType = EntitiesType ; pub type NotationType = :: std :: string :: String ; pub type NameType = :: std :: string :: String ; pub type QNameType = :: std :: string :: String ; pub type AnySimpleType = :: std :: string :: String ; pub type AnyUriType = :: std :: string :: String ; pub type Base64BinaryType = :: std :: string :: String ; pub type BooleanType = :: core :: primitive :: bool ; pub type ByteType = :: core :: primitive :: i8 ; pub type DateType = :: std :: string :: String ; pub type DateTimeType = :: std :: string :: String ; pub type DecimalType = :: core :: primitive :: f64 ; pub type DoubleType = :: core :: primitive :: f64 ; pub type DurationType = :: std :: string :: String ; pub type FloatType = :: core :: primitive :: f32 ; pub type GDayType = :: std :: string :: String ; pub type GMonthType = :: std :: string :: String ; pub type GMonthDayType = :: std :: string :: String ; pub type GYearType = :: std :: string :: String ; pub type GYearMonthType = :: std :: string :: String ; pub type HexBinaryType = :: std :: string :: String ; pub type IntType = :: core :: primitive :: i32 ; pub type IntegerType = :: core :: primitive :: i32 ; pub type LanguageType = :: std :: string :: String ; pub type LongType = :: core :: primitive :: i64 ; pub type NegativeIntegerType = :: core :: primitive :: isize ; pub type NonNegativeIntegerType = :: core :: primitive :: usize ; pub type NonPositiveIntegerType = :: core :: primitive :: isize ; pub type NormalizedStringType = :: std :: string :: String ; pub type PositiveIntegerType = :: core :: primitive :: usize ; pub type ShortType = :: core :: primitive :: i16 ; pub type StringType = :: std :: string :: String ; pub type TimeType = :: std :: string :: String ; pub type TokenType = :: std :: string :: String ; pub type UnsignedByteType = :: core :: primitive :: u8 ; pub type UnsignedIntType = :: core :: primitive :: u32 ; pub type UnsignedLongType = :: core :: primitive :: u64 ; pub type UnsignedShortType = :: core :: primitive :: u16 ; } \ No newline at end of file diff --git a/cmtool-assemble/src/parser/mod.rs b/cmtool-assemble/src/parser/mod.rs new file mode 100644 index 00000000..9005a5cb --- /dev/null +++ b/cmtool-assemble/src/parser/mod.rs @@ -0,0 +1,39 @@ +#[allow(clippy::all)] +#[rustfmt::skip] +pub mod generated_domain; +use crate::{CMError, DomainData}; +mod reactors; +use reactors::{parse_connection, parse_feed, parse_reactor}; + +pub fn parse_domain(root: &generated_domain::RootElementType) -> Result { + let info = parse_reactor(&root.reactors)?; + + let mut mass_balance = reactors::PfrGlobalMassBalance::new(&info.pfr_names); + + let raw_connections = root + .connections + .as_ref() + .map(|connections| parse_connection(&info, connections, &mut mass_balance)); + + if let Some(feeds) = &root.feeds { + let connections = parse_feed(&info, feeds, &mut mass_balance)?; //TODO + } + mass_balance.validate()?; + + Ok(DomainData { + connections: raw_connections, + info, + }) +} + +pub fn get_root(content: &str) -> Result { + // let cursor = Cursor::new(content.as_bytes()); + // let mut reader = IoReader::new(cursor).with_error_info(); + // let root = generated_domain::Root::deserialize(&mut reader).unwrap(); + let root = serde_xml_rs::from_str::(content)?; + eprintln!("WARNING: Some reactor may miss if xml is not parsed correctly"); + if root.version != 3 { + panic!("ALED"); + } + Ok(root) +} diff --git a/cmtool-assemble/src/parser/reactors.rs b/cmtool-assemble/src/parser/reactors.rs new file mode 100644 index 00000000..84fbe84a --- /dev/null +++ b/cmtool-assemble/src/parser/reactors.rs @@ -0,0 +1,188 @@ +use std::collections::HashMap; + +use super::generated_domain; +use crate::{ + CMError, + data::{DomainInfo, FlowDirection}, +}; +use cmtool_data::{PhaseCM, RawDataFlux}; + +#[derive(Default, Copy, Clone, Debug)] +struct FlowData { + in_flow: f64, + out_flow: f64, +} + +#[derive(Copy, Clone, Debug, Default)] +struct PhaseFlow { + gas: FlowData, + liquid: FlowData, +} + +// Struct to hold flow data for each ID using a HashMap +#[derive(Default, Debug)] +pub(super) struct PfrGlobalMassBalance { + flows: HashMap, +} + +impl PfrGlobalMassBalance { + pub(super) fn new(pfr_names: &[String]) -> Self { + let mut flows = HashMap::new(); + for name in pfr_names { + flows.insert(name.clone(), PhaseFlow::default()); + } + PfrGlobalMassBalance { flows } + } + + pub(super) fn validate(&self) -> Result<(), CMError> { + for (i, flow) in &self.flows { + if flow.gas.in_flow != flow.gas.out_flow { + return Err(CMError::MassBalance(i.clone(), "gas".to_owned())); + } + if flow.liquid.in_flow != flow.liquid.out_flow { + return Err(CMError::MassBalance(i.clone(), "liquid".to_owned())); + } + } + Ok(()) + } + + fn update_flow(&mut self, id: &str, phase: PhaseCM, direction: FlowDirection, vflow: f64) { + if let Some(phase_flow) = self.flows.get_mut(id) { + let flow_data = match phase { + PhaseCM::Gas => &mut phase_flow.gas, + PhaseCM::Liquid => &mut phase_flow.liquid, + }; + + match direction { + FlowDirection::In => flow_data.in_flow += vflow, + FlowDirection::Out => flow_data.out_flow += vflow, + } + } + } +} + +fn connection_per_phase( + info: &DomainInfo, + mass_balance: &mut PfrGlobalMassBalance, + phase_node: &[generated_domain::FluxType], + phase: PhaseCM, +) -> cmtool_data::RawDataFlux { + let n_node = phase_node.len(); + + let mut rd = RawDataFlux::new(info.total_number_compartment, n_node); + + for (node, flux) in phase_node.iter().zip(rd.fluxes.iter_mut()) { + if let (Some(absolute_source_id), Some(absolute_target_id)) = ( + info.get_relative_compartment_number(&node.source.id, node.source.compartment_id), + info.get_relative_compartment_number(&node.target.id, node.target.compartment_id), + ) { + let flow = f64::from(node.value.content); + flux.id_source = absolute_source_id as u32; + flux.id_target = absolute_target_id as u32; + flux.flux_source_target = flow; + flux.flux_target_source = 0.; //Connection is pure PlugFLow + + let f_pfr_source = info.pfr_names.contains(&node.source.id); + let is_same_node = + absolute_source_id == absolute_target_id && node.source.id == node.target.id; + + if is_same_node && f_pfr_source { + mass_balance.update_flow(&node.target.id, phase, FlowDirection::Out, flow); + } else { + let f_pfr_target = info.pfr_names.contains(&node.target.id); + if f_pfr_target { + mass_balance.update_flow(&node.target.id, phase, FlowDirection::In, flow); + } + if f_pfr_source { + mass_balance.update_flow(&node.source.id, phase, FlowDirection::Out, flow); + } + } + } + } + rd +} + +pub fn parse_connection( + info: &DomainInfo, + connections: &generated_domain::ConnectionsType, + mass_balance: &mut PfrGlobalMassBalance, +) -> [RawDataFlux; 2] { + let liquid_connection: Vec = connections + .flux + .iter() + .filter(|f| f.phase == *"liquid") + .cloned() + .collect(); + + let gas_connection: Vec = connections + .flux + .iter() + .filter(|f| f.phase == *"gas") + .cloned() + .collect(); + + [ + connection_per_phase(info, mass_balance, &liquid_connection, PhaseCM::Liquid), + connection_per_phase(info, mass_balance, &gas_connection, PhaseCM::Gas), + ] +} + +fn convert_feed_flux_to_flux(feed: generated_domain::FeedFluxType) -> generated_domain::FluxType { + generated_domain::FluxType { + source: feed.source, + target: feed.target, + phase: feed.phase, + value: feed.value, + } +} + +pub fn parse_feed( + info: &DomainInfo, + feeds: &generated_domain::FeedsType, + mass_balance: &mut PfrGlobalMassBalance, +) -> Result<(),CMError> { + let liquid_feed: Vec = feeds + .flux + .iter() + .filter(|f| f.phase == *"liquid") + .map(|feed| convert_feed_flux_to_flux(feed.clone())) + .collect(); + let gas_feed: Vec = feeds + .flux + .iter() + .filter(|f| f.phase == *"gas") + .map(|feed| convert_feed_flux_to_flux(feed.clone())) + .collect(); + connection_per_phase(info, mass_balance, &liquid_feed, PhaseCM::Liquid); + connection_per_phase(info, mass_balance, &gas_feed, PhaseCM::Gas); + Ok(()) +} + +pub fn parse_reactor(reactors: &generated_domain::ReactorsType) -> Result { + let mut parseinfo = DomainInfo::default(); + + let mut in_place_cumsum = 0; + for reactor in &reactors.content { + match reactor { + generated_domain::ReactorsTypeContent::Reactor0D(reactor0_dtype) => { + parseinfo + .compartment_cumsum + .insert(reactor0_dtype.id.clone(), in_place_cumsum); + parseinfo.total_number_compartment += 1; + in_place_cumsum += 1; + } + generated_domain::ReactorsTypeContent::Reactor1D(current_pfr) => { + parseinfo + .compartment_cumsum + .insert(current_pfr.id.clone(), in_place_cumsum); + parseinfo.total_number_compartment += current_pfr.compartments; + parseinfo.pfr_names.push(current_pfr.id.clone()); + in_place_cumsum += current_pfr.compartments; + } + generated_domain::ReactorsTypeContent::Reactor3D(reactor3_dtype) => { + todo!("{:?}", reactor3_dtype) + } + } + } + Ok(parseinfo) +} diff --git a/cmtool/Cargo.toml b/cmtool/Cargo.toml index 382a6ee3..25037e28 100644 --- a/cmtool/Cargo.toml +++ b/cmtool/Cargo.toml @@ -10,6 +10,7 @@ license.workspace = true clap.workspace = true cmtool-core.workspace = true cmtool-data.workspace = true +cmtool-assemble.workspace = true thiserror.workspace = true diff --git a/cmtool/src/args/mod.rs b/cmtool/src/args/mod.rs index de07b8e6..577b7b24 100644 --- a/cmtool/src/args/mod.rs +++ b/cmtool/src/args/mod.rs @@ -35,8 +35,7 @@ pub enum Mode { } #[derive(Parser, Clone)] -#[clap(name = "cmtool")] -pub struct GenArgs { +pub struct CfdGenerate { #[clap(flatten)] pub common: CommonArgs, @@ -44,25 +43,47 @@ pub struct GenArgs { pub mode: Mode, } +#[derive(Parser, Clone)] +pub struct XMLGenerate { + #[clap(short, long)] + pub descriptor_path: String, + #[clap(short, long)] + pub out_dir: Option, +} + +#[derive(Subcommand, Clone)] +pub enum AllModes { + Cfd(CfdGenerate), + XML(XMLGenerate), +} + +#[derive(Parser, Clone)] +#[clap(name = "cmtool")] +pub struct GenArgs { + #[clap(subcommand)] + pub mode: AllModes, +} + + impl GenArgs { - #[cfg(debug_assertions)] - pub fn get() -> Self { - GenArgs { - common: CommonArgs { - n_i: 3, - n_j: 3, - n_k: 3, - out: None, - verbose: true, - }, - mode: Mode::Auto(AutoArgs { - case_path: - "/home/benjamin/Documents/thesis/cfd-cma/sanofi_cfd/inputs/RESULTS.encas" - .to_string(), - }), - } - } - #[cfg(not(debug_assertions))] + // #[cfg(debug_assertions)] + // pub fn get() -> Self { + // GenArgs { + // common: CommonArgs { + // n_i: 3, + // n_j: 3, + // n_k: 3, + // out: None, + // verbose: true, + // }, + // mode: Mode::Auto(AutoArgs { + // case_path: + // "/home/benjamin/Documents/thesis/cfd-cma/sanofi_cfd/inputs/RESULTS.encas" + // .to_string(), + // }), + // } + // } + // #[cfg(not(debug_assertions))] pub fn get() -> Self { GenArgs::parse() } diff --git a/cmtool/src/lib.rs b/cmtool/src/lib.rs index 943574b4..bf789e94 100644 --- a/cmtool/src/lib.rs +++ b/cmtool/src/lib.rs @@ -1,5 +1,19 @@ // SPDX-License-Identifier: GPL-3.0-or-later -mod generators; + mod sanitizer; -pub use generators::*; pub use sanitizer::*; +use thiserror::Error; +#[derive(Error, Debug)] +pub enum CmtoolError { + #[error("Cmtool: {0}")] + Data(#[from] cmtool_data::DataError), + + #[error("Cmtool: {0}")] + Core(#[from] cmtool_core::CoreError), + + #[error("Cmtool Assemble: {0}")] + Assemble(#[from] cmtool_assemble::CMError), + + #[error("Cmtool: {0}")] + Custom(String), +} \ No newline at end of file diff --git a/cmtool/src/main.rs b/cmtool/src/main.rs index 20cd8548..5dd1e1b9 100644 --- a/cmtool/src/main.rs +++ b/cmtool/src/main.rs @@ -3,17 +3,34 @@ use cmtool::CmtoolError; use std::fmt::Write; +use std::path::PathBuf; +use std::process::ExitCode; use std::{env, path::Path}; mod args; use args::*; +fn out_or_default(out:Option)->String{ + out + .unwrap_or(format!("{}/../out/", env!("CARGO_MANIFEST_DIR"))) +} + fn main() -> Result<(), CmtoolError> { let args = GenArgs::get(); let mode = args.mode; match mode { - Mode::Auto(autoargs) => auto_main(args.common, autoargs), - - Mode::Manual(manual_args) => todo!(), + AllModes::Cfd(cfdargs) => match cfdargs.mode { + Mode::Auto(autoargs) => auto_main(cfdargs.common, autoargs), + + Mode::Manual(manual_args) => todo!(), + }, + AllModes::XML(xml) => { + let path = PathBuf::from(out_or_default(xml.out_dir)); + let contents = std::fs::read_to_string(xml.descriptor_path).expect("Read file"); + let root_dir = path.to_str().unwrap().to_owned(); + std::fs::create_dir_all(&root_dir).expect("mkdir"); + cmtool_assemble::generate_domain(&root_dir, &contents)?; + Ok(()) + } } } @@ -23,9 +40,9 @@ fn auto_main(common: CommonArgs, autoargs: AutoArgs) -> Result<(), CmtoolError> .and_then(|s| s.to_str()) .unwrap(); // Converts OsStr to &str - let root_dir = common - .out - .unwrap_or(format!("{}/../out/", env!("CARGO_MANIFEST_DIR"))); + + + let root_dir = out_or_default(common.out); let case = cmtool_core::ensight_gold::case::Case::read(&autoargs.case_path)?; diff --git a/example/Cargo.toml b/examples/Cargo.toml similarity index 54% rename from example/Cargo.toml rename to examples/Cargo.toml index 9d68c76a..2dc5bdc6 100644 --- a/example/Cargo.toml +++ b/examples/Cargo.toml @@ -13,7 +13,27 @@ cmtool-data.workspace = true nalgebra.workspace = true nalgebra-sparse.workspace=true ndarray.workspace=true - +cmtool-assemble.workspace = true [[example]] path = "src/mixing_simple.rs" name = "mixing_simple" + + + +[[example]] +name = "case_0d1d" +path = "src/map_generation/case_0d1d.rs" + +[[example]] +name = "simple_0d" +path = "src/map_generation/simple_0d.rs" + +[[example]] +name = "simple_1d" +path = "src/map_generation/simple_1d.rs" + +[[example]] +name = "neubauer" +path = "src/map_generation/neubauer.rs" + + diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..851b8fd5 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,5 @@ +# Case Example + +## Reactor and Connection Template + +- **example_1**: Script with an XML example for the full configuration of multiple connected reactors. diff --git a/examples/data/case_0d1d/reactors.xml b/examples/data/case_0d1d/reactors.xml new file mode 100644 index 00000000..e245b58c --- /dev/null +++ b/examples/data/case_0d1d/reactors.xml @@ -0,0 +1,72 @@ + + + + + + + 0.01 + 3 + + + 0.2 + 100 + 9e-9 + + + + 10 + + 0.31 + + + + + + + 1e-6 + + + + + 1e-6 + + + + + + 1e-5 + + + + + 1e-5 + + + + + + + + 2e-5 + + + + + + 6e-5 + + + + + + 3e-8 + + + + + + 6e-8 + + + + diff --git a/examples/data/case_0d1d_liq/reactors.xml b/examples/data/case_0d1d_liq/reactors.xml new file mode 100644 index 00000000..a5965e59 --- /dev/null +++ b/examples/data/case_0d1d_liq/reactors.xml @@ -0,0 +1,50 @@ + + + + + + + 0.01 + 3 + + + 0. + 100 + 9e-9 + + + + 10 + + 0. + + + + + + + 1e-6 + + + + + 1e-6 + + + + + + + + + 3e-8 + + + + + + 6e-8 + + + + diff --git a/examples/data/neubauer/reactors.xml b/examples/data/neubauer/reactors.xml new file mode 100644 index 00000000..d6b44799 --- /dev/null +++ b/examples/data/neubauer/reactors.xml @@ -0,0 +1,47 @@ + + + + + + + 0.0192 + 3 + + + 0.2 + 100 + 0 + + + + + 9.305e-3 + + 0.37 + + + + + + + 6.1e-6 + + + + + 6.1e-6 + + + + + + + 2.33333333333333E-05 + + + + + 6.66666666666667E-05 + + + \ No newline at end of file diff --git a/examples/data/neubauer/reactors_batch.xml b/examples/data/neubauer/reactors_batch.xml new file mode 100644 index 00000000..38a0f8fd --- /dev/null +++ b/examples/data/neubauer/reactors_batch.xml @@ -0,0 +1,45 @@ + + + + + + + + 0.0192 + 3 + + + 0.2 + 100 + 0 + + + + 9.305e-3 + + 0.37 + + + + + + + + 2.33333333333333E-05 + + + + + 6.1e-6 + + + + + + + + 6.66666666666667E-05 + + + + diff --git a/examples/data/simple_0d/reactors.xml b/examples/data/simple_0d/reactors.xml new file mode 100644 index 00000000..d8008b15 --- /dev/null +++ b/examples/data/simple_0d/reactors.xml @@ -0,0 +1,17 @@ + + + + + + + 10.0 + + + + 0.0 + + + + + + diff --git a/examples/data/simple_0d1d/reactors.xml b/examples/data/simple_0d1d/reactors.xml new file mode 100644 index 00000000..588f211d --- /dev/null +++ b/examples/data/simple_0d1d/reactors.xml @@ -0,0 +1,48 @@ + + + + + + + 0.01 + 1 + + + 0.2 + 66 + 0 + + + + 10 + + 0.1 + + + + + + + + + 6.1e-6 + + + + + 6.1e-6 + + + + + + 4.1e-6 + + + + + 4.1e-6 + + + + diff --git a/examples/data/simple_1d/reactors.xml b/examples/data/simple_1d/reactors.xml new file mode 100644 index 00000000..8a57a75f --- /dev/null +++ b/examples/data/simple_1d/reactors.xml @@ -0,0 +1,28 @@ + + + + + + + 0.01 + 1 + + + 0.2 + 50 + 1e-9 + + + + + + + 2.33333333333333E-05 + + + + + 4.33333333333333E-05 + + + diff --git a/example/python/mixing_simple.py b/examples/python/mixing_simple.py similarity index 100% rename from example/python/mixing_simple.py rename to examples/python/mixing_simple.py diff --git a/examples/src/lib.rs b/examples/src/lib.rs new file mode 100644 index 00000000..fb145070 --- /dev/null +++ b/examples/src/lib.rs @@ -0,0 +1,4 @@ +pub mod map_generation; + + + diff --git a/examples/src/map_generation/case_0d1d.rs b/examples/src/map_generation/case_0d1d.rs new file mode 100644 index 00000000..db65421b --- /dev/null +++ b/examples/src/map_generation/case_0d1d.rs @@ -0,0 +1,7 @@ +use cmtool_example::map_generation::generate; + +fn main() { + generate( + "examples/data/case_0d1d/reactors.xml" + ); +} diff --git a/examples/src/map_generation/mod.rs b/examples/src/map_generation/mod.rs new file mode 100644 index 00000000..a77f0870 --- /dev/null +++ b/examples/src/map_generation/mod.rs @@ -0,0 +1,45 @@ +use std::path::PathBuf; + +pub fn generate(reactor_input_file_name: &str) -> bool { + let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../out/examples/"); + let contents = std::fs::read_to_string(reactor_input_file_name).expect("getcontent"); + let root_dir = path.to_str().unwrap().to_owned(); + std::fs::create_dir_all(&root_dir).expect("mkdir"); + + if let Ok(mut domain) = cmtool_assemble::generate_domain(&root_dir, &contents) { + println!("OK"); + return true; + } + + eprintln!("Error domain"); + false +} + +#[cfg(test)] +mod domain_integration_test { + use super::*; + + fn common_test(name: &str) { + let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join(format!("../examples/data/{}/reactors.xml", name)); + + assert!(generate(path.to_str().unwrap())); + } + + #[test] + fn case_0d1d() { + common_test("case_0d1d"); + } + #[test] + fn fcase_0d() { + common_test("simple_0d"); + } + #[test] + fn simple_0d1d() { + common_test("simple_0d1d"); + } + #[test] + fn case_0d1d_liq() { + common_test("case_0d1d_liq"); + } +} diff --git a/examples/src/map_generation/neubauer.rs b/examples/src/map_generation/neubauer.rs new file mode 100644 index 00000000..71ffa81a --- /dev/null +++ b/examples/src/map_generation/neubauer.rs @@ -0,0 +1,10 @@ +use cmtool_example::map_generation::generate; + +fn main() { + generate( + "examples/data/neubauer/reactors.xml", + ); + generate( + "examples/data/neubauer/reactors_batch.xml", + ); +} diff --git a/examples/src/map_generation/simple_0d.rs b/examples/src/map_generation/simple_0d.rs new file mode 100644 index 00000000..7432f7d9 --- /dev/null +++ b/examples/src/map_generation/simple_0d.rs @@ -0,0 +1,7 @@ +use cmtool_example::map_generation::generate; + +fn main() { + generate( + "examples/data/simple_0d/reactors.xml", + ); +} diff --git a/examples/src/map_generation/simple_1d.rs b/examples/src/map_generation/simple_1d.rs new file mode 100644 index 00000000..f413acc3 --- /dev/null +++ b/examples/src/map_generation/simple_1d.rs @@ -0,0 +1,7 @@ +use cmtool_example::map_generation::generate; + +fn main() { + generate( + "examples/data/simple_1d/reactors.xml", + ); +} diff --git a/example/src/mixing_simple.rs b/examples/src/mixing_simple.rs similarity index 100% rename from example/src/mixing_simple.rs rename to examples/src/mixing_simple.rs From 3406d0f1e0c5dddbd9949c5847448542fb0964c9 Mon Sep 17 00:00:00 2001 From: Casale Date: Sun, 23 Nov 2025 16:30:26 +0100 Subject: [PATCH 03/44] feat(xml): correct feed parsing --- cmtool-assemble/src/data.rs | 15 +++ cmtool-assemble/src/lib.rs | 16 ++- cmtool-assemble/src/map_generation.rs | 16 +-- cmtool-assemble/src/parser/mod.rs | 15 ++- cmtool-assemble/src/parser/pfr_mb.rs | 111 ++++++++++++++++++++ cmtool-assemble/src/parser/reactors.rs | 138 +++++++++++++------------ cmtool/src/args/mod.rs | 25 ++++- cmtool/src/main.rs | 7 +- examples/src/map_generation/mod.rs | 13 +-- 9 files changed, 259 insertions(+), 97 deletions(-) create mode 100644 cmtool-assemble/src/parser/pfr_mb.rs diff --git a/cmtool-assemble/src/data.rs b/cmtool-assemble/src/data.rs index 837079bc..e87a4d58 100644 --- a/cmtool-assemble/src/data.rs +++ b/cmtool-assemble/src/data.rs @@ -1,6 +1,20 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; +#[derive(Debug, Serialize, Deserialize, Clone, Copy)] +pub struct FeedFlow { + pub flow: f64, + pub position: usize, + pub output_position: Option, +} + + + +#[derive(Default, Clone, Serialize, Deserialize)] +pub struct ParsedFeeds { + pub liq: HashMap, + pub gas: HashMap, +} #[derive(Default, Clone, Serialize, Deserialize)] pub struct DomainInfo { pub compartment_cumsum: HashMap, @@ -12,6 +26,7 @@ pub struct DomainInfo { pub struct DomainData { pub connections: Option<[cmtool_data::RawDataFlux; 2]>, pub info: DomainInfo, + pub feeds: Option, } diff --git a/cmtool-assemble/src/lib.rs b/cmtool-assemble/src/lib.rs index 14467471..e41c256d 100644 --- a/cmtool-assemble/src/lib.rs +++ b/cmtool-assemble/src/lib.rs @@ -37,9 +37,21 @@ pub enum CMError { pub fn generate_domain(root_dir: &str, reactor_content: &str) -> Result { let root = get_root(reactor_content)?; - let domain = parse_domain(&root)?; + let (domain,mb) = parse_domain(&root)?; let root_dir = format!("{}/{}", root_dir, root.run_id); std::fs::create_dir_all(root_dir.clone())?; - generate_flowmap(&root_dir, &domain, &root.reactors)?; + generate_flowmap(&root_dir, &domain, &root.reactors,&mb)?; Ok(domain) } + + +pub fn headless_generate( + reactor_input_file_name: &str, + path: impl AsRef, +) -> Result<(),CMError>{ + let contents = std::fs::read_to_string(reactor_input_file_name)?; + let root_dir = path.as_ref().to_str().expect("path str").to_owned(); + std::fs::create_dir_all(&root_dir)?; + generate_domain(&root_dir, &contents)?; + Ok(()) +} \ No newline at end of file diff --git a/cmtool-assemble/src/map_generation.rs b/cmtool-assemble/src/map_generation.rs index be40f63c..6b4ae423 100644 --- a/cmtool-assemble/src/map_generation.rs +++ b/cmtool-assemble/src/map_generation.rs @@ -1,8 +1,9 @@ use crate::CMError; use crate::data::DomainData; use crate::generators::{Generator, PFRDescription}; -use crate::parser::generated_domain::{self, Reactor0DType}; -use cmtool_data::CMCaseReader; +use crate::parser::generated_domain; +use crate::parser::{PfrGlobalMassBalance,generated_domain::{Reactor0DType}}; +use cmtool_data::{CMCaseReader, PhaseCM}; use cmtool_data::{CCMCaseInfo, CMCaseWriter, DataError}; fn get_volume(size: &generated_domain::GeneralSizeType) -> f64 { @@ -54,6 +55,7 @@ fn _generate_reactor_1d( root: &str, ids: &mut Vec, current_pfr: &generated_domain::Reactor1DType, + mb: &PfrGlobalMassBalance, ) -> Result<(), CMError> { ids.push(current_pfr.id.clone()); let path = format!("{}/{}", root, current_pfr.id); @@ -75,8 +77,8 @@ fn _generate_reactor_1d( n_compartment: current_pfr.compartments, length: dim.length.content.into(), diameter: dim.diameter.content.into(), - liquid_flow: 1.0, - gas_flow: 0.0, + liquid_flow: mb.get_flow(¤t_pfr.id, PhaseCM::Liquid)?, + gas_flow: mb.get_flow(¤t_pfr.id, PhaseCM::Gas)?, gas_fraction: current_pfr.volume_fraction.content as f64, axial_dispersion: 1e-9, }; @@ -95,6 +97,7 @@ fn generate_partial_flowmap( generator: &mut Generator, root: &str, reactors: &generated_domain::ReactorsType, + mb: &PfrGlobalMassBalance, ) -> Result, CMError> { let mut ids = Vec::with_capacity(reactors.content.len()); @@ -104,7 +107,7 @@ fn generate_partial_flowmap( _generate_reactor_0d::(save_intermediate, generator, root, &mut ids, r)?; } generated_domain::ReactorsTypeContent::Reactor1D(r) => { - _generate_reactor_1d::(save_intermediate, generator, root, &mut ids, r)?; + _generate_reactor_1d::(save_intermediate, generator, root, &mut ids, r,mb)?; } generated_domain::ReactorsTypeContent::Reactor3D(reactor3_dtype) => { todo!("{:?}", reactor3_dtype) @@ -118,6 +121,7 @@ pub fn generate_flowmap( root: &str, domain: &DomainData, reactors: &generated_domain::ReactorsType, + mb: &PfrGlobalMassBalance, ) -> Result<(), CMError> { let mut generator = Generator::new(); let mut save_intermediate = false; @@ -126,7 +130,7 @@ pub fn generate_flowmap( } let _ids = - generate_partial_flowmap::(save_intermediate, &mut generator, root, reactors)?; + generate_partial_flowmap::(save_intermediate, &mut generator, root, reactors,mb)?; if _ids.len() > 1 { if save_intermediate { diff --git a/cmtool-assemble/src/parser/mod.rs b/cmtool-assemble/src/parser/mod.rs index 9005a5cb..675f9882 100644 --- a/cmtool-assemble/src/parser/mod.rs +++ b/cmtool-assemble/src/parser/mod.rs @@ -5,25 +5,30 @@ use crate::{CMError, DomainData}; mod reactors; use reactors::{parse_connection, parse_feed, parse_reactor}; -pub fn parse_domain(root: &generated_domain::RootElementType) -> Result { +mod pfr_mb; +pub(super) use pfr_mb::PfrGlobalMassBalance; + +pub fn parse_domain(root: &generated_domain::RootElementType) -> Result<(DomainData,PfrGlobalMassBalance), CMError> { let info = parse_reactor(&root.reactors)?; - let mut mass_balance = reactors::PfrGlobalMassBalance::new(&info.pfr_names); + let mut mass_balance = PfrGlobalMassBalance::new(&info.pfr_names); let raw_connections = root .connections .as_ref() .map(|connections| parse_connection(&info, connections, &mut mass_balance)); + let mut pfeeds = None; if let Some(feeds) = &root.feeds { - let connections = parse_feed(&info, feeds, &mut mass_balance)?; //TODO + pfeeds = Some(parse_feed(&info, feeds, &mut mass_balance)); //TODO } mass_balance.validate()?; - Ok(DomainData { + Ok((DomainData { connections: raw_connections, info, - }) + feeds: pfeeds, + },mass_balance)) } pub fn get_root(content: &str) -> Result { diff --git a/cmtool-assemble/src/parser/pfr_mb.rs b/cmtool-assemble/src/parser/pfr_mb.rs new file mode 100644 index 00000000..a5eb8fd9 --- /dev/null +++ b/cmtool-assemble/src/parser/pfr_mb.rs @@ -0,0 +1,111 @@ +use std::collections::HashMap; + +use cmtool_data::PhaseCM; + +use crate::{CMError, data::FlowDirection}; + +#[derive(Default, Copy, Clone, Debug)] +struct FlowData { + in_flow: f64, + out_flow: f64, +} + +#[derive(Copy, Clone, Debug, Default)] +pub struct PhaseFlow { + gas: FlowData, + liquid: FlowData, +} + +// Struct to hold flow data for each ID using a HashMap +#[derive(Default, Debug)] +pub struct PfrGlobalMassBalance { + flows: HashMap, + validated: bool, +} + +impl PfrGlobalMassBalance { + pub(super) fn new(pfr_names: &[String]) -> Self { + let mut flows = HashMap::new(); + for name in pfr_names { + flows.insert(name.clone(), PhaseFlow::default()); + } + PfrGlobalMassBalance { + flows, + validated: false, + } + } + + pub(super) fn validate(&mut self) -> Result<(), CMError> { + for (i, flow) in &self.flows { + if flow.gas.in_flow != flow.gas.out_flow { + return Err(CMError::MassBalance(i.clone(), "gas".to_owned())); + } + if flow.liquid.in_flow != flow.liquid.out_flow { + return Err(CMError::MassBalance(i.clone(), "liquid".to_owned())); + } + } + self.validated = true; + Ok(()) + } + + pub(crate) fn update_flow( + &mut self, + id: &str, + phase: PhaseCM, + direction: FlowDirection, + vflow: f64, + ) { + self.validated = false; + if let Some(phase_flow) = self.flows.get_mut(id) { + let flow_data = match phase { + PhaseCM::Gas => &mut phase_flow.gas, + PhaseCM::Liquid => &mut phase_flow.liquid, + }; + + match direction { + FlowDirection::In => flow_data.in_flow += vflow, + FlowDirection::Out => flow_data.out_flow += vflow, + } + } + } + pub fn get_flow(&self, id: &str, phase: PhaseCM) -> Result { + if !self.validated { + return Err(CMError::Custom("Mass balance needs to be validated before being accessed".to_owned())); + } + + if let Some(phase_flow) = self.flows.get(id) { + return Ok(match phase { + PhaseCM::Gas => phase_flow.gas.in_flow, + PhaseCM::Liquid => phase_flow.liquid.in_flow, + }); + } + Err(CMError::Custom(format!("Mass balance does not provide {} pfr",id))) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_pfr_mass_balance() { + let names = ["name1".to_owned(), "name2".to_owned()]; + let mut mb = PfrGlobalMassBalance::new(&names); + + let expected_ok = mb.validate(); //BEcause flow is null + assert!(expected_ok.is_ok()); + + mb.update_flow("name1", PhaseCM::Gas, FlowDirection::In, 4.); + let expected_err = mb.validate(); + assert!(expected_err.is_err()); + + mb.update_flow("name1", PhaseCM::Gas, FlowDirection::Out, 2.); + mb.update_flow("name1", PhaseCM::Gas, FlowDirection::Out, 2.); + + let expected_ok = mb.validate(); + assert!(expected_ok.is_ok()); + + let pfr_flow = mb.get_flow("name1", PhaseCM::Gas).unwrap(); + assert!(pfr_flow == 4.) + } +} diff --git a/cmtool-assemble/src/parser/reactors.rs b/cmtool-assemble/src/parser/reactors.rs index 84fbe84a..8b00d500 100644 --- a/cmtool-assemble/src/parser/reactors.rs +++ b/cmtool-assemble/src/parser/reactors.rs @@ -1,66 +1,16 @@ use std::collections::HashMap; +use super::PfrGlobalMassBalance; use super::generated_domain; +use crate::data::FeedFlow; +use crate::data::ParsedFeeds; +use crate::parser::generated_domain::FeedFluxType; use crate::{ CMError, data::{DomainInfo, FlowDirection}, }; use cmtool_data::{PhaseCM, RawDataFlux}; -#[derive(Default, Copy, Clone, Debug)] -struct FlowData { - in_flow: f64, - out_flow: f64, -} - -#[derive(Copy, Clone, Debug, Default)] -struct PhaseFlow { - gas: FlowData, - liquid: FlowData, -} - -// Struct to hold flow data for each ID using a HashMap -#[derive(Default, Debug)] -pub(super) struct PfrGlobalMassBalance { - flows: HashMap, -} - -impl PfrGlobalMassBalance { - pub(super) fn new(pfr_names: &[String]) -> Self { - let mut flows = HashMap::new(); - for name in pfr_names { - flows.insert(name.clone(), PhaseFlow::default()); - } - PfrGlobalMassBalance { flows } - } - - pub(super) fn validate(&self) -> Result<(), CMError> { - for (i, flow) in &self.flows { - if flow.gas.in_flow != flow.gas.out_flow { - return Err(CMError::MassBalance(i.clone(), "gas".to_owned())); - } - if flow.liquid.in_flow != flow.liquid.out_flow { - return Err(CMError::MassBalance(i.clone(), "liquid".to_owned())); - } - } - Ok(()) - } - - fn update_flow(&mut self, id: &str, phase: PhaseCM, direction: FlowDirection, vflow: f64) { - if let Some(phase_flow) = self.flows.get_mut(id) { - let flow_data = match phase { - PhaseCM::Gas => &mut phase_flow.gas, - PhaseCM::Liquid => &mut phase_flow.liquid, - }; - - match direction { - FlowDirection::In => flow_data.in_flow += vflow, - FlowDirection::Out => flow_data.out_flow += vflow, - } - } - } -} - fn connection_per_phase( info: &DomainInfo, mass_balance: &mut PfrGlobalMassBalance, @@ -97,6 +47,8 @@ fn connection_per_phase( mass_balance.update_flow(&node.source.id, phase, FlowDirection::Out, flow); } } + } else { + eprintln!("Ignored connection"); } } rd @@ -136,26 +88,76 @@ fn convert_feed_flux_to_flux(feed: generated_domain::FeedFluxType) -> generated_ } } -pub fn parse_feed( +// pub fn parse_feed( +// info: &DomainInfo, +// feeds: &generated_domain::FeedsType, +// mass_balance: &mut PfrGlobalMassBalance, +// ) -> Result<(), CMError> { +// let liquid_feed: Vec = feeds +// .flux +// .iter() +// .filter(|f| f.phase == *"liquid") +// .map(|feed| convert_feed_flux_to_flux(feed.clone())) +// .collect(); +// let gas_feed: Vec = feeds +// .flux +// .iter() +// .filter(|f| f.phase == *"gas") +// .map(|feed| convert_feed_flux_to_flux(feed.clone())) +// .collect(); +// connection_per_phase(info, mass_balance, &liquid_feed, PhaseCM::Liquid); +// connection_per_phase(info, mass_balance, &gas_feed, PhaseCM::Gas); +// Ok(()) +// } +fn parse_feed_phase( info: &DomainInfo, - feeds: &generated_domain::FeedsType, + feeds: &[&FeedFluxType], + phase: PhaseCM, mass_balance: &mut PfrGlobalMassBalance, -) -> Result<(),CMError> { - let liquid_feed: Vec = feeds - .flux +) -> HashMap { + let fluxes: Vec = feeds .iter() - .filter(|f| f.phase == *"liquid") - .map(|feed| convert_feed_flux_to_flux(feed.clone())) + .map(|feed| generated_domain::FluxType { + source: feed.source.clone(), + target: feed.target.clone(), + phase: feed.phase.clone(), + value: feed.value.clone(), + }) .collect(); - let gas_feed: Vec = feeds + + let id: Vec = feeds.iter().map(|feed| feed.id.clone()).collect(); + let rd = connection_per_phase(info, mass_balance, &fluxes, phase); + + rd.fluxes + .iter() + .zip(id) + .map(|(flux, id)| { + ( + id, + FeedFlow { + flow: flux.flux_source_target, + position: flux.id_source as usize, + output_position: None, //TODO + }, + ) + }) + .collect() +} +pub fn parse_feed( + info: &DomainInfo, + feeds: &generated_domain::FeedsType, + mass_balance: &mut PfrGlobalMassBalance, +) -> ParsedFeeds { + let (liquid_feeds, gas_feeds): (Vec<_>, Vec<_>) = feeds .flux .iter() - .filter(|f| f.phase == *"gas") - .map(|feed| convert_feed_flux_to_flux(feed.clone())) - .collect(); - connection_per_phase(info, mass_balance, &liquid_feed, PhaseCM::Liquid); - connection_per_phase(info, mass_balance, &gas_feed, PhaseCM::Gas); - Ok(()) + .partition(|f| f.phase == *"liquid") + .clone(); + + ParsedFeeds { + liq: parse_feed_phase(info, &liquid_feeds, PhaseCM::Liquid, mass_balance), + gas: parse_feed_phase(info, &gas_feeds, PhaseCM::Gas, mass_balance), + } } pub fn parse_reactor(reactors: &generated_domain::ReactorsType) -> Result { diff --git a/cmtool/src/args/mod.rs b/cmtool/src/args/mod.rs index 577b7b24..d510cbec 100644 --- a/cmtool/src/args/mod.rs +++ b/cmtool/src/args/mod.rs @@ -54,11 +54,32 @@ pub struct XMLGenerate { #[derive(Subcommand, Clone)] pub enum AllModes { Cfd(CfdGenerate), - XML(XMLGenerate), + Xml(XMLGenerate), } #[derive(Parser, Clone)] -#[clap(name = "cmtool")] +#[command( + name = "CMTool", + author, + version, + about = "Command line interface to generate Compartment Models", + help_template = "\ +{name} {version} + +{about} + +USAGE: + {usage} + +OPTIONS: +{options} + +COMMANDS: +{subcommands} + +By {author} +" +)] pub struct GenArgs { #[clap(subcommand)] pub mode: AllModes, diff --git a/cmtool/src/main.rs b/cmtool/src/main.rs index 5dd1e1b9..37a09939 100644 --- a/cmtool/src/main.rs +++ b/cmtool/src/main.rs @@ -23,12 +23,9 @@ fn main() -> Result<(), CmtoolError> { Mode::Manual(manual_args) => todo!(), }, - AllModes::XML(xml) => { + AllModes::Xml(xml) => { let path = PathBuf::from(out_or_default(xml.out_dir)); - let contents = std::fs::read_to_string(xml.descriptor_path).expect("Read file"); - let root_dir = path.to_str().unwrap().to_owned(); - std::fs::create_dir_all(&root_dir).expect("mkdir"); - cmtool_assemble::generate_domain(&root_dir, &contents)?; + cmtool_assemble::headless_generate(&xml.descriptor_path, path)?; Ok(()) } } diff --git a/examples/src/map_generation/mod.rs b/examples/src/map_generation/mod.rs index a77f0870..f493e886 100644 --- a/examples/src/map_generation/mod.rs +++ b/examples/src/map_generation/mod.rs @@ -2,17 +2,12 @@ use std::path::PathBuf; pub fn generate(reactor_input_file_name: &str) -> bool { let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../out/examples/"); - let contents = std::fs::read_to_string(reactor_input_file_name).expect("getcontent"); - let root_dir = path.to_str().unwrap().to_owned(); - std::fs::create_dir_all(&root_dir).expect("mkdir"); - - if let Ok(mut domain) = cmtool_assemble::generate_domain(&root_dir, &contents) { - println!("OK"); - return true; + if let Err(msg)=cmtool_assemble::headless_generate(reactor_input_file_name, path){ + eprintln!("{}",msg); + return false; } + return true; - eprintln!("Error domain"); - false } #[cfg(test)] From 7f13d207d4881217371d04d97bc27b2ea0110343 Mon Sep 17 00:00:00 2001 From: Casale Date: Sun, 23 Nov 2025 16:37:47 +0100 Subject: [PATCH 04/44] chore: add spdx header --- cmtool-assemble/build.rs | 4 +++ cmtool-assemble/datamodel/connections.xsd | 3 ++ cmtool-assemble/datamodel/main.xsd | 5 +++- cmtool-assemble/datamodel/reactors.xsd | 3 ++ cmtool-assemble/datamodel/units.xsd | 3 ++ cmtool-assemble/src/data.rs | 35 ++++++++++++----------- cmtool-assemble/src/generators.rs | 3 ++ cmtool-assemble/src/lib.rs | 3 ++ cmtool-assemble/src/map_generation.rs | 9 +++--- cmtool-assemble/src/parser/mod.rs | 3 ++ cmtool-assemble/src/parser/pfr_mb.rs | 3 ++ cmtool-assemble/src/parser/reactors.rs | 3 ++ cmtool-cxx/build.rs | 2 ++ cmtool-cxx/src/lib.rs | 2 ++ 14 files changed, 60 insertions(+), 21 deletions(-) diff --git a/cmtool-assemble/build.rs b/cmtool-assemble/build.rs index 340072f9..daf18f10 100644 --- a/cmtool-assemble/build.rs +++ b/cmtool-assemble/build.rs @@ -1,3 +1,7 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + + + use crate::fs::File; use std::fs; use std::io::Write; diff --git a/cmtool-assemble/datamodel/connections.xsd b/cmtool-assemble/datamodel/connections.xsd index 553fe73e..da0836dd 100644 --- a/cmtool-assemble/datamodel/connections.xsd +++ b/cmtool-assemble/datamodel/connections.xsd @@ -1,3 +1,6 @@ + diff --git a/cmtool-assemble/datamodel/main.xsd b/cmtool-assemble/datamodel/main.xsd index 0abe838e..731475f8 100644 --- a/cmtool-assemble/datamodel/main.xsd +++ b/cmtool-assemble/datamodel/main.xsd @@ -1,4 +1,7 @@ - + diff --git a/cmtool-assemble/src/data.rs b/cmtool-assemble/src/data.rs index e87a4d58..b85e794a 100644 --- a/cmtool-assemble/src/data.rs +++ b/cmtool-assemble/src/data.rs @@ -1,6 +1,15 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + + use serde::{Deserialize, Serialize}; use std::collections::HashMap; +#[derive(Debug, PartialEq)] +pub enum FlowDirection { + In, + Out, +} + #[derive(Debug, Serialize, Deserialize, Clone, Copy)] pub struct FeedFlow { pub flow: f64, @@ -8,8 +17,6 @@ pub struct FeedFlow { pub output_position: Option, } - - #[derive(Default, Clone, Serialize, Deserialize)] pub struct ParsedFeeds { pub liq: HashMap, @@ -21,16 +28,6 @@ pub struct DomainInfo { pub total_number_compartment: usize, pub pfr_names: Vec, } - -#[derive(Clone, Serialize, Deserialize, Default)] -pub struct DomainData { - pub connections: Option<[cmtool_data::RawDataFlux; 2]>, - pub info: DomainInfo, - pub feeds: Option, -} - - - impl DomainInfo { pub fn get_relative_compartment_number( &self, @@ -43,8 +40,14 @@ impl DomainInfo { } } -#[derive(Debug, PartialEq)] -pub enum FlowDirection { - In, - Out, +#[derive(Clone, Serialize, Deserialize, Default)] +pub struct DomainData { + pub connections: Option<[cmtool_data::RawDataFlux; 2]>, + pub info: DomainInfo, + pub feeds: Option, } + + + + + diff --git a/cmtool-assemble/src/generators.rs b/cmtool-assemble/src/generators.rs index 98aa3a86..53fc97b9 100644 --- a/cmtool-assemble/src/generators.rs +++ b/cmtool-assemble/src/generators.rs @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + + use crate::CMError; use cmtool_data::{ diff --git a/cmtool-assemble/src/lib.rs b/cmtool-assemble/src/lib.rs index e41c256d..7868b82b 100644 --- a/cmtool-assemble/src/lib.rs +++ b/cmtool-assemble/src/lib.rs @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + + mod data; mod generators; mod map_generation; diff --git a/cmtool-assemble/src/map_generation.rs b/cmtool-assemble/src/map_generation.rs index 6b4ae423..88cef999 100644 --- a/cmtool-assemble/src/map_generation.rs +++ b/cmtool-assemble/src/map_generation.rs @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + + use crate::CMError; use crate::data::DomainData; use crate::generators::{Generator, PFRDescription}; @@ -124,10 +127,8 @@ pub fn generate_flowmap( mb: &PfrGlobalMassBalance, ) -> Result<(), CMError> { let mut generator = Generator::new(); - let mut save_intermediate = false; - if reactors.content.len() == 1 { - save_intermediate = true; - } + + let save_intermediate = reactors.content.len() == 1; let _ids = generate_partial_flowmap::(save_intermediate, &mut generator, root, reactors,mb)?; diff --git a/cmtool-assemble/src/parser/mod.rs b/cmtool-assemble/src/parser/mod.rs index 675f9882..464dd57f 100644 --- a/cmtool-assemble/src/parser/mod.rs +++ b/cmtool-assemble/src/parser/mod.rs @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + + #[allow(clippy::all)] #[rustfmt::skip] pub mod generated_domain; diff --git a/cmtool-assemble/src/parser/pfr_mb.rs b/cmtool-assemble/src/parser/pfr_mb.rs index a5eb8fd9..2e415384 100644 --- a/cmtool-assemble/src/parser/pfr_mb.rs +++ b/cmtool-assemble/src/parser/pfr_mb.rs @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + + use std::collections::HashMap; use cmtool_data::PhaseCM; diff --git a/cmtool-assemble/src/parser/reactors.rs b/cmtool-assemble/src/parser/reactors.rs index 8b00d500..475fa617 100644 --- a/cmtool-assemble/src/parser/reactors.rs +++ b/cmtool-assemble/src/parser/reactors.rs @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + + use std::collections::HashMap; use super::PfrGlobalMassBalance; diff --git a/cmtool-cxx/build.rs b/cmtool-cxx/build.rs index 3eed60ea..96f01fcc 100644 --- a/cmtool-cxx/build.rs +++ b/cmtool-cxx/build.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + fn main() { cxx_build::bridge("src/lib.rs") .flag_if_supported("-std=c++20") diff --git a/cmtool-cxx/src/lib.rs b/cmtool-cxx/src/lib.rs index 439da0d5..dbbca5f7 100644 --- a/cmtool-cxx/src/lib.rs +++ b/cmtool-cxx/src/lib.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + use std::ptr::null; use cmtool_data::{ From 8b7aac0a26e13e32dbd398362ae4b4fb3c1931de Mon Sep 17 00:00:00 2001 From: Casale Date: Sun, 23 Nov 2025 17:58:08 +0100 Subject: [PATCH 05/44] refractor --- cmtool-assemble/src/generators.rs | 16 ++++++---------- cmtool-assemble/src/lib.rs | 4 ++-- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/cmtool-assemble/src/generators.rs b/cmtool-assemble/src/generators.rs index 53fc97b9..689c9ea9 100644 --- a/cmtool-assemble/src/generators.rs +++ b/cmtool-assemble/src/generators.rs @@ -71,11 +71,7 @@ fn filter_phase(raw_phase: &[RawPhase], phase: PhaseCM) -> Vec { } impl Generator { - fn f_write(f: impl RawData, r: &str, p: &str) -> Result<(), CMError> { - f.write_raw(&format!("{}/{}", r, p))?; - Ok(()) - } - + pub fn new() -> Self { Self { raw_phase: Default::default(), @@ -315,15 +311,15 @@ impl Generator { connections: Option<[RawDataFlux; 2]>, ) -> Result<(), CMError> { - + const MERGE_FOLDER_NAME:&str = "merged"; let liquid_phase = filter_phase(&self.raw_phase,PhaseCM::Liquid); let gasphase = filter_phase(&self.raw_phase,PhaseCM::Gas); - let path = format!("{}/merged", dest); + let path = format!("{}/{}", dest,MERGE_FOLDER_NAME); std::fs::create_dir_all(&path)?; //FIXME let mut case = CMCase::default(); // case.n_div = n_div; - let relative = Some(String::from("merged")); //TODO Clean this + let relative = Some(String::from(MERGE_FOLDER_NAME)); let liquid_connection = connections.as_ref().map(|c| c[0].clone()); let gas_connection = connections.as_ref().map(|c| c[1].clone()); @@ -336,8 +332,8 @@ impl Generator { Self::write_phase(&path, &mut case, phase, relative)?; } - CMCaseJson::write_case(case.clone(), Path::new(&format!("{}/jcma_case", dest)))?; - cmtool_data::CCMCaseInfo::write_case(case, Path::new(&format!("{}/cma_case", dest)))?; + CMCaseJson::write_case(case.clone(), Path::new(&format!("{}/cma_case", dest)))?; + // cmtool_data::CCMCaseInfo::write_case(case, Path::new(&format!("{}/cma_case", dest)))?; Ok(()) } diff --git a/cmtool-assemble/src/lib.rs b/cmtool-assemble/src/lib.rs index 7868b82b..de43cd76 100644 --- a/cmtool-assemble/src/lib.rs +++ b/cmtool-assemble/src/lib.rs @@ -12,7 +12,7 @@ pub use crate::data::{DomainData, DomainInfo}; use crate::parser::{get_root, parse_domain}; use map_generation::generate_flowmap; use thiserror::Error; - +pub use data::{FeedFlow,ParsedFeeds}; #[derive(Error, Debug)] pub enum CMError { #[error("Cmtool encountered an unknown error. Please check the input and try again.")] @@ -57,4 +57,4 @@ pub fn headless_generate( std::fs::create_dir_all(&root_dir)?; generate_domain(&root_dir, &contents)?; Ok(()) -} \ No newline at end of file +} From ba74c6112d0f6d10c6271054fdf3f1acb75cc2dd Mon Sep 17 00:00:00 2001 From: bcasale Date: Wed, 26 Nov 2025 12:15:08 +0100 Subject: [PATCH 06/44] doc(example): add reactive example --- documentations/docbook/src/components.md | 25 +++ .../docbook/src/tuto/basic_mixing.md | 4 +- examples/python/reactive_mixing.py | 153 ++++++++++++++++++ examples/src/mixing_simple.rs | 3 +- 4 files changed, 181 insertions(+), 4 deletions(-) create mode 100644 examples/python/reactive_mixing.py diff --git a/documentations/docbook/src/components.md b/documentations/docbook/src/components.md index 65f3524a..8c298583 100644 --- a/documentations/docbook/src/components.md +++ b/documentations/docbook/src/components.md @@ -7,3 +7,28 @@ This crates aims to provides different feature to perform complete simulation us - A CFD-to-CMA generator to use CFD results exported in Ensight-Gold Format +# Crate organization + +## cmtool + +Simple CLI tool for fast use. +Generation from CFD case or from xml descriptor + +## cmtool-data + +Utilities to manipulation Compartment Data such as Flowmaps and Scalar Fields. High level manipultation is done with FlowMap transitioner, low-level is used to directly write Compartment data. + +## cmtool-core +CFD-to-CMA: Algorithm to transform results from fine-mesh transient CFD simuation into Compartment Models. + +## cmtool-assemble + +Generate flowmaps from from models and assemble them. Flowmaps descriptor as XML file can be used from high-level use. Direct generate and case writer is available for low-level use. + +## cmtool-python + +Python bindings, namely binds cmtool-core generation from Ensight case, FlowmapTransitioner and case reading. + +## cmtool-cxx + +Bindings for c++ use, expose FlowMapTranstioner API \ No newline at end of file diff --git a/documentations/docbook/src/tuto/basic_mixing.md b/documentations/docbook/src/tuto/basic_mixing.md index 922910fd..b86b7cae 100644 --- a/documentations/docbook/src/tuto/basic_mixing.md +++ b/documentations/docbook/src/tuto/basic_mixing.md @@ -35,7 +35,7 @@ Our Python wrapper offers an easy-to-use interface for out-of-the-box simulation ```python -{{#include ../../../../example/python/mixing_simple.py}} +{{#include ../../../example/python/mixing_simple.py}} ``` @@ -48,5 +48,5 @@ Here is an example of how to implement this in Rust. The **CMTool** components a Using Rust is advisable for building complete simulation tools. Rust allows developers to focus on writing performant code and provides more control over data, although it may require more development effort. ```rust -{{#include ../../../../example/src/mixing_simple.rs}} +{{#include ../../../example/src/mixing_simple.rs}} ``` diff --git a/examples/python/reactive_mixing.py b/examples/python/reactive_mixing.py new file mode 100644 index 00000000..c634b2e4 --- /dev/null +++ b/examples/python/reactive_mixing.py @@ -0,0 +1,153 @@ +""" +Simple Example Script: Reactive and Mixing Simulation with pycmtool +====================================================== + +**Purpose:** +This script demonstrates how to use the `pycmtool` library to perform a simple mixing simulation. +It integrates a mass balance over time using a sparse matrix representation of the system +The core functionality is used for chemical or compartmental mixing simulations. + +**Key Features:** +- Uses `pycmtool` to construct flow sparse matrices from system states. +- Integrates mass balance equations using `scipy.integrate.solve_ivp`. +- Visualizes results with `matplotlib`. + +**Example Workflow:** +1. Define initial mass distribution. +2. Use `pycmtool` to advance the system state and construct sparse matrices. +3. Integrate the system over a specified duration. +4. Plot the results. + +**Author:** CASALE Benjamin +**Date:** 2025-11-24 +**Version:** 1.0 +""" + +import os + +import matplotlib.pyplot as plt +import numpy as np +import pycmtool +from scipy.integrate import solve_ivp + +##### MONOD.py +N_SPECIES = 2 # We use 2 species to demonstrates that pycmtool can handle different dissolved species + +K = 1e-2 +mu = 0.8 / 3600 +K_S = 0.1 + + +def monod(C): + return mu * C[0, :] / (C[0, :] + K_S) + + +##### + + +def integration( + fmt: "pycmtool.Transitioner", mass_0: np.ndarray, duration: float, n_species +): + """ + Perform a mixing simulation by integrating the mass balance ODE over a specified duration. + + This function uses `pycmtool` to advance the system state and construct sparse matrices, + then integrates the mass balance equations using `scipy.integrate.solve_ivp`. + + Args: + fmt: A `pycmtool.Transitioner` object to advance the system state. + mass_0: Initial mass distribution (1D array of shape `(n_species * n_compartments,)`). + duration: Total simulation time (in seconds or any consistent time unit). + n_species: Number of species in the simulation . + """ + f = monod + + def wrap(t: float, x: np.ndarray, f) -> np.ndarray: + """Wrapper function for ODE integration.""" + d_t = 0 # Not used for this transitioner + # Advance iterator to the corresponding flowmap (according to t or dt) + it = fmt.advance(t, d_t) + # Get the transition matrix + transition = pycmtool.get_sparse_transition_matrix(it) + n_c = transition.shape[0] # Number of compartments + vol = it.volumes # Volume of each compartment + _mass = x.reshape((n_species, n_c)) # Reshape to (N_SPECIES, n_compartments) + C = _mass / vol # Concentration: mass / volume + R = np.zeros_like(C) + r = f(C) * vol + R[0, :] = -r + R[1, :] = r + return (C @ transition + R).reshape(-1) # Return flattened array for ODE solver + + return solve_ivp( + wrap, + (0, duration), + mass_0.reshape(-1), + method="BDF", + vectorized=False, + ) + + +def initial_c_distribution(n_c): + """ + Generate a random initial concentration distribution for a simulation with `n_c` compartments. + """ + return np.random.random((1, n_c)) + # m = np.zeros((n_c,)) + # m[0] = 1 + # return m + + +def get_normalized(it, y, i): + """ + Calculate the normalized concentration for a given species across all compartments. + + The normalization is performed by dividing each compartment's concentration by the mean concentration + of the species across all compartments. This is useful for comparing relative concentrations. + """ + vol = it.volumes + m0_c = y[0, :, i] + return (m0_c / vol) / np.mean(m0_c / vol, axis=0) + + +def check_mixing(fmt, final_time: float): + it = fmt.get_current() + transition = pycmtool.get_sparse_transition_matrix(it) + n_c = transition.shape[0] + + C = np.zeros((N_SPECIES, n_c)) + C[0, :] = initial_c_distribution(n_c) + vol = it.volumes + m0 = C * vol + + sol = integration(fmt, m0, final_time, N_SPECIES) + y = sol.y.reshape((N_SPECIES, n_c, -1)) + it = fmt.get_current() + m0_c = y[0, :, 0] + mt_c = y[0, :, -1] + c_init = get_normalized(fmt.get_at(0), y, 0) + c_final = get_normalized(fmt.get_at(fmt.n_flowmaps - 1), y, -1) + + m0m = np.sum(m0_c, axis=0) + mfm = np.sum(mt_c, axis=0) + + print("Inital mass: ", m0m) + print("Final mass: ", mfm) + print("Initial normalized C: ", c_init[:5]) + print("Final normalized C: ", c_final[:5]) + print("Final variance: ", np.var(c_final, axis=0)) + plt.figure() + plt.plot(sol.t, y[0, 0, :]) + plt.plot(sol.t, y[1, 0, :]) + plt.title("Concentration in compartment0") + plt.legend() + plt.show() + + +if __name__ == "__main__": + final_time = 100 + root = os.environ["EXAMPLE_ROOT"] + # Let CMTool read and load the full case automatically, ready to iterate + + fmt = pycmtool.data.get_transitioner(root) + check_mixing(fmt, final_time) diff --git a/examples/src/mixing_simple.rs b/examples/src/mixing_simple.rs index 8a4b823d..b07de52a 100644 --- a/examples/src/mixing_simple.rs +++ b/examples/src/mixing_simple.rs @@ -119,10 +119,9 @@ fn check_mixing(mut fm_t: T, final_time: f64, n_step: us fn main() { let final_time: f64 = 50.; let n_step: usize = 5000; - //All fonction use generic, specify iterator type here let root = std::env::var("EXAMPLE_ROOT").unwrap(); - + //All fonctions use generic, specify iterator type here let t: DiscontinuousTransitioner = get_transitioner(&root).unwrap(); check_mixing(t, final_time, n_step); } From 79b8a2e83e73095a2bbd972920468db06cd81d76 Mon Sep 17 00:00:00 2001 From: bcasale Date: Wed, 26 Nov 2025 12:16:33 +0100 Subject: [PATCH 07/44] feat(data): use only json case, add method to determine residence_time --- cmtool-assemble/src/map_generation.rs | 26 ++++++++++++++----------- cmtool-data/src/case.rs | 19 ++++++++++++++++++ cmtool-data/src/lib.rs | 28 +++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 11 deletions(-) diff --git a/cmtool-assemble/src/map_generation.rs b/cmtool-assemble/src/map_generation.rs index 88cef999..67964862 100644 --- a/cmtool-assemble/src/map_generation.rs +++ b/cmtool-assemble/src/map_generation.rs @@ -1,13 +1,12 @@ // SPDX-License-Identifier: GPL-3.0-or-later - use crate::CMError; use crate::data::DomainData; use crate::generators::{Generator, PFRDescription}; use crate::parser::generated_domain; -use crate::parser::{PfrGlobalMassBalance,generated_domain::{Reactor0DType}}; -use cmtool_data::{CMCaseReader, PhaseCM}; +use crate::parser::{PfrGlobalMassBalance, generated_domain::Reactor0DType}; use cmtool_data::{CCMCaseInfo, CMCaseWriter, DataError}; +use cmtool_data::{CMCaseJson, CMCaseReader, PhaseCM}; fn get_volume(size: &generated_domain::GeneralSizeType) -> f64 { match &size { @@ -80,7 +79,7 @@ fn _generate_reactor_1d( n_compartment: current_pfr.compartments, length: dim.length.content.into(), diameter: dim.diameter.content.into(), - liquid_flow: mb.get_flow(¤t_pfr.id, PhaseCM::Liquid)?, + liquid_flow: mb.get_flow(¤t_pfr.id, PhaseCM::Liquid)?, gas_flow: mb.get_flow(¤t_pfr.id, PhaseCM::Gas)?, gas_fraction: current_pfr.volume_fraction.content as f64, axial_dispersion: 1e-9, @@ -100,7 +99,7 @@ fn generate_partial_flowmap( generator: &mut Generator, root: &str, reactors: &generated_domain::ReactorsType, - mb: &PfrGlobalMassBalance, + mb: &PfrGlobalMassBalance, ) -> Result, CMError> { let mut ids = Vec::with_capacity(reactors.content.len()); @@ -110,7 +109,7 @@ fn generate_partial_flowmap( _generate_reactor_0d::(save_intermediate, generator, root, &mut ids, r)?; } generated_domain::ReactorsTypeContent::Reactor1D(r) => { - _generate_reactor_1d::(save_intermediate, generator, root, &mut ids, r,mb)?; + _generate_reactor_1d::(save_intermediate, generator, root, &mut ids, r, mb)?; } generated_domain::ReactorsTypeContent::Reactor3D(reactor3_dtype) => { todo!("{:?}", reactor3_dtype) @@ -127,11 +126,16 @@ pub fn generate_flowmap( mb: &PfrGlobalMassBalance, ) -> Result<(), CMError> { let mut generator = Generator::new(); - + let save_intermediate = reactors.content.len() == 1; - let _ids = - generate_partial_flowmap::(save_intermediate, &mut generator, root, reactors,mb)?; + let _ids = generate_partial_flowmap::( + save_intermediate, + &mut generator, + root, + reactors, + mb, + )?; if _ids.len() > 1 { if save_intermediate { @@ -142,9 +146,9 @@ pub fn generate_flowmap( } else if _ids.len() == 1 && save_intermediate { let case_path = format!("{}/{}/cma_case", root, _ids[0]); let prep = format!("./{}", _ids[0]); - let case = CCMCaseInfo::read_case(std::path::Path::new(&case_path))?.prepend_path(&prep); + let case = CMCaseJson::read_case(std::path::Path::new(&case_path))?.prepend_path(&prep); - CCMCaseInfo::write_case(case, std::path::Path::new(&format!("{}/cma_case", root)))?; + CMCaseJson::write_case(case, std::path::Path::new(&format!("{}/cma_case", root)))?; } Ok(()) diff --git a/cmtool-data/src/case.rs b/cmtool-data/src/case.rs index a23856f7..5926124c 100644 --- a/cmtool-data/src/case.rs +++ b/cmtool-data/src/case.rs @@ -2,6 +2,7 @@ use crate::{CMAExportType, DataError}; use serde::{Deserialize, Serialize}; +use std::ffi::os_str::Display; use std::io::{BufReader, Read, Write}; use std::{collections::HashMap, fs, path::Path}; @@ -25,6 +26,24 @@ pub struct CMCase { pub is_reursive: bool, } +impl std::fmt::Display for CMCase { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "CMCase Configuration:")?; + writeln!(f, " - Number of Divisions: [{}x{}x{}]", self.n_div[0], self.n_div[1], self.n_div[2])?; + writeln!(f, " - Description: {}", self.description)?; + writeln!(f, " - Time per Flow Map: {:.2} seconds", self.time_per_flow_map)?; + + // Show paths, iterating over the HashMap + writeln!(f, " - Export Paths:\n")?; + for export_type in self.paths.keys() { + writeln!(f, " - {:?}\n", export_type)?; + } + + writeln!(f, " - Recursive: {}", if self.is_reursive { "Yes" } else { "No" })?; + Ok(()) + } +} + impl CMCase { pub fn n_compartment(&self) -> u32 { self.n_div.iter().product() diff --git a/cmtool-data/src/lib.rs b/cmtool-data/src/lib.rs index 11c11304..e3a45cd4 100644 --- a/cmtool-data/src/lib.rs +++ b/cmtool-data/src/lib.rs @@ -14,6 +14,7 @@ pub use rawdata::{ ScalarFileHeader, ScalarValueType, }; pub use states::*; +use core::f64; use std::io; use thiserror::Error; pub use transitioner::*; @@ -63,6 +64,33 @@ pub fn get_transitioner(root: &str) -> Result(fmt: &T) -> f64 { + let n_states = fmt.size(); + let mut min_all = f64::MAX; + + for i_state in 0..n_states { + let state = fmt.get_at(i_state) + .expect("Transitioner error: n_state != real stored states"); + + if state.liquid.out_flows.len() != state.liquid.volumes.len() { + panic!("Mismatched lengths between out_flows and volumes."); + } + + let min_i = state.liquid.out_flows.iter() + .zip(state.liquid.volumes.iter()) + .map(|(&f, &v)| f / v) + .filter(|&x| x.is_finite()) + .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Greater)) + .expect("Should exist a minimum for the state"); + + min_all = f64::min(min_all,min_i); + } + + min_all +} From ac457477cbddf6485be36626a754b1265bf2694f Mon Sep 17 00:00:00 2001 From: bcasale Date: Wed, 26 Nov 2025 12:17:41 +0100 Subject: [PATCH 08/44] feat(generation/parser): improve parsing exposed function to be used from external programs --- cmtool-assemble/build.rs | 4 -- cmtool-assemble/src/data.rs | 6 --- cmtool-assemble/src/generators.rs | 13 +++---- cmtool-assemble/src/lib.rs | 47 +++++++++++++++++++----- cmtool-assemble/src/parser/mod.rs | 17 ++++++--- cmtool-assemble/src/parser/pfr_mb.rs | 12 ++++-- cmtool-assemble/src/parser/reactors.rs | 1 - cmtool-cxx/src/lib.rs | 2 +- cmtool-data/src/case.rs | 20 ++++++++-- cmtool-data/src/lib.rs | 27 ++++++++------ cmtool/src/args/mod.rs | 1 - cmtool/src/lib.rs | 2 +- cmtool/src/main.rs | 9 ++--- examples/src/lib.rs | 3 -- examples/src/map_generation/case_0d1d.rs | 4 +- examples/src/map_generation/mod.rs | 5 +-- examples/src/map_generation/neubauer.rs | 8 +--- examples/src/map_generation/simple_0d.rs | 4 +- examples/src/map_generation/simple_1d.rs | 4 +- 19 files changed, 104 insertions(+), 85 deletions(-) diff --git a/cmtool-assemble/build.rs b/cmtool-assemble/build.rs index daf18f10..fb99a7ce 100644 --- a/cmtool-assemble/build.rs +++ b/cmtool-assemble/build.rs @@ -1,7 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later - - use crate::fs::File; use std::fs; use std::io::Write; @@ -55,8 +53,6 @@ fn domain_schema() -> Result<(), Error> { Ok(()) } - - fn main() -> Result<(), Error> { domain_schema()?; println!("cargo:rerun-if-changed=cmtool-assemble/build.rs"); diff --git a/cmtool-assemble/src/data.rs b/cmtool-assemble/src/data.rs index b85e794a..3d075c5c 100644 --- a/cmtool-assemble/src/data.rs +++ b/cmtool-assemble/src/data.rs @@ -1,6 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later - use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -46,8 +45,3 @@ pub struct DomainData { pub info: DomainInfo, pub feeds: Option, } - - - - - diff --git a/cmtool-assemble/src/generators.rs b/cmtool-assemble/src/generators.rs index 689c9ea9..c38081be 100644 --- a/cmtool-assemble/src/generators.rs +++ b/cmtool-assemble/src/generators.rs @@ -1,6 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later - use crate::CMError; use cmtool_data::{ @@ -71,7 +70,6 @@ fn filter_phase(raw_phase: &[RawPhase], phase: PhaseCM) -> Vec { } impl Generator { - pub fn new() -> Self { Self { raw_phase: Default::default(), @@ -310,12 +308,11 @@ impl Generator { dest: &str, connections: Option<[RawDataFlux; 2]>, ) -> Result<(), CMError> { - - const MERGE_FOLDER_NAME:&str = "merged"; - let liquid_phase = filter_phase(&self.raw_phase,PhaseCM::Liquid); - let gasphase = filter_phase(&self.raw_phase,PhaseCM::Gas); - - let path = format!("{}/{}", dest,MERGE_FOLDER_NAME); + const MERGE_FOLDER_NAME: &str = "merged"; + let liquid_phase = filter_phase(&self.raw_phase, PhaseCM::Liquid); + let gasphase = filter_phase(&self.raw_phase, PhaseCM::Gas); + + let path = format!("{}/{}", dest, MERGE_FOLDER_NAME); std::fs::create_dir_all(&path)?; //FIXME let mut case = CMCase::default(); // case.n_div = n_div; diff --git a/cmtool-assemble/src/lib.rs b/cmtool-assemble/src/lib.rs index de43cd76..db1f7f9d 100644 --- a/cmtool-assemble/src/lib.rs +++ b/cmtool-assemble/src/lib.rs @@ -1,6 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later - mod data; mod generators; mod map_generation; @@ -9,10 +8,10 @@ mod parser; use std::io; pub use crate::data::{DomainData, DomainInfo}; -use crate::parser::{get_root, parse_domain}; +use crate::parser::{generated_domain::RootElementType, get_root, parse_domain}; +pub use data::{FeedFlow, ParsedFeeds}; use map_generation::generate_flowmap; use thiserror::Error; -pub use data::{FeedFlow,ParsedFeeds}; #[derive(Error, Debug)] pub enum CMError { #[error("Cmtool encountered an unknown error. Please check the input and try again.")] @@ -38,20 +37,48 @@ pub enum CMError { Parse(#[from] serde_xml_rs::Error), } +pub struct Parser(RootElementType); + +impl Parser { + pub fn start_parsing(reactor_content: &str) -> Result<(String, Self), CMError> { + let root = get_root(reactor_content)?; + Ok((root.run_id.clone(), Self(root))) + } + fn continue_parsing(p: Parser, root_dir: &str) -> Result { + // let (domain, mb) = parse_domain(&root)?; + // let root_dir = format!("{}/{}", root_dir, root.run_id); + // std::fs::create_dir_all(root_dir.clone())?; + // generate_flowmap(&root_dir, &domain, &root.reactors, &mb)?; + let path = format!("{}/{}", root_dir, p.0.run_id); + // Ok(domain) + Self::continue_parsing_with_path(p, path.as_str()) + } + + pub fn continue_parsing_with_path( + Parser(root): Parser, + root_dir: &str, + ) -> Result { + let (domain, mb) = parse_domain(&root)?; + let root_dir = format!("{}", root_dir); + std::fs::create_dir_all(root_dir.clone())?; + generate_flowmap(&root_dir, &domain, &root.reactors, &mb)?; + + Ok(domain) + } +} + pub fn generate_domain(root_dir: &str, reactor_content: &str) -> Result { - let root = get_root(reactor_content)?; - let (domain,mb) = parse_domain(&root)?; - let root_dir = format!("{}/{}", root_dir, root.run_id); - std::fs::create_dir_all(root_dir.clone())?; - generate_flowmap(&root_dir, &domain, &root.reactors,&mb)?; + let (_id, root) = Parser::start_parsing(reactor_content)?; + + let domain = Parser::continue_parsing(root, root_dir)?; + Ok(domain) } - pub fn headless_generate( reactor_input_file_name: &str, path: impl AsRef, -) -> Result<(),CMError>{ +) -> Result<(), CMError> { let contents = std::fs::read_to_string(reactor_input_file_name)?; let root_dir = path.as_ref().to_str().expect("path str").to_owned(); std::fs::create_dir_all(&root_dir)?; diff --git a/cmtool-assemble/src/parser/mod.rs b/cmtool-assemble/src/parser/mod.rs index 464dd57f..85fae659 100644 --- a/cmtool-assemble/src/parser/mod.rs +++ b/cmtool-assemble/src/parser/mod.rs @@ -11,7 +11,9 @@ use reactors::{parse_connection, parse_feed, parse_reactor}; mod pfr_mb; pub(super) use pfr_mb::PfrGlobalMassBalance; -pub fn parse_domain(root: &generated_domain::RootElementType) -> Result<(DomainData,PfrGlobalMassBalance), CMError> { +pub fn parse_domain( + root: &generated_domain::RootElementType, +) -> Result<(DomainData, PfrGlobalMassBalance), CMError> { let info = parse_reactor(&root.reactors)?; let mut mass_balance = PfrGlobalMassBalance::new(&info.pfr_names); @@ -27,11 +29,14 @@ pub fn parse_domain(root: &generated_domain::RootElementType) -> Result<(DomainD } mass_balance.validate()?; - Ok((DomainData { - connections: raw_connections, - info, - feeds: pfeeds, - },mass_balance)) + Ok(( + DomainData { + connections: raw_connections, + info, + feeds: pfeeds, + }, + mass_balance, + )) } pub fn get_root(content: &str) -> Result { diff --git a/cmtool-assemble/src/parser/pfr_mb.rs b/cmtool-assemble/src/parser/pfr_mb.rs index 2e415384..a0aca26f 100644 --- a/cmtool-assemble/src/parser/pfr_mb.rs +++ b/cmtool-assemble/src/parser/pfr_mb.rs @@ -1,6 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later - use std::collections::HashMap; use cmtool_data::PhaseCM; @@ -38,7 +37,7 @@ impl PfrGlobalMassBalance { } } - pub(super) fn validate(&mut self) -> Result<(), CMError> { + pub(super) fn validate(&mut self) -> Result<(), CMError> { for (i, flow) in &self.flows { if flow.gas.in_flow != flow.gas.out_flow { return Err(CMError::MassBalance(i.clone(), "gas".to_owned())); @@ -73,7 +72,9 @@ impl PfrGlobalMassBalance { } pub fn get_flow(&self, id: &str, phase: PhaseCM) -> Result { if !self.validated { - return Err(CMError::Custom("Mass balance needs to be validated before being accessed".to_owned())); + return Err(CMError::Custom( + "Mass balance needs to be validated before being accessed".to_owned(), + )); } if let Some(phase_flow) = self.flows.get(id) { @@ -82,7 +83,10 @@ impl PfrGlobalMassBalance { PhaseCM::Liquid => phase_flow.liquid.in_flow, }); } - Err(CMError::Custom(format!("Mass balance does not provide {} pfr",id))) + Err(CMError::Custom(format!( + "Mass balance does not provide {} pfr", + id + ))) } } diff --git a/cmtool-assemble/src/parser/reactors.rs b/cmtool-assemble/src/parser/reactors.rs index 475fa617..a9842906 100644 --- a/cmtool-assemble/src/parser/reactors.rs +++ b/cmtool-assemble/src/parser/reactors.rs @@ -1,6 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later - use std::collections::HashMap; use super::PfrGlobalMassBalance; diff --git a/cmtool-cxx/src/lib.rs b/cmtool-cxx/src/lib.rs index dbbca5f7..8a34a479 100644 --- a/cmtool-cxx/src/lib.rs +++ b/cmtool-cxx/src/lib.rs @@ -156,7 +156,7 @@ impl TransitionerWrapper { fn get_dtransitioner(root: &str) -> Result, String> { match get_transitioner(root) { Ok(t) => Ok(Box::new(TransitionerWrapper(t))), - Err(d) => Err(format!("{}", d)), + Err(d) => Err(format!("Error while reading {}: {}", root, d)), } } diff --git a/cmtool-data/src/case.rs b/cmtool-data/src/case.rs index 5926124c..b8764961 100644 --- a/cmtool-data/src/case.rs +++ b/cmtool-data/src/case.rs @@ -29,17 +29,29 @@ pub struct CMCase { impl std::fmt::Display for CMCase { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!(f, "CMCase Configuration:")?; - writeln!(f, " - Number of Divisions: [{}x{}x{}]", self.n_div[0], self.n_div[1], self.n_div[2])?; + writeln!( + f, + " - Number of Divisions: [{}x{}x{}]", + self.n_div[0], self.n_div[1], self.n_div[2] + )?; writeln!(f, " - Description: {}", self.description)?; - writeln!(f, " - Time per Flow Map: {:.2} seconds", self.time_per_flow_map)?; - + writeln!( + f, + " - Time per Flow Map: {:.2} seconds", + self.time_per_flow_map + )?; + // Show paths, iterating over the HashMap writeln!(f, " - Export Paths:\n")?; for export_type in self.paths.keys() { writeln!(f, " - {:?}\n", export_type)?; } - writeln!(f, " - Recursive: {}", if self.is_reursive { "Yes" } else { "No" })?; + writeln!( + f, + " - Recursive: {}", + if self.is_reursive { "Yes" } else { "No" } + )?; Ok(()) } } diff --git a/cmtool-data/src/lib.rs b/cmtool-data/src/lib.rs index e3a45cd4..671305cd 100644 --- a/cmtool-data/src/lib.rs +++ b/cmtool-data/src/lib.rs @@ -7,6 +7,7 @@ mod rawdata; mod states; mod transitioner; pub use case::{CCMCaseInfo, CMCase, CMCaseJson, CMCaseReader, CMCaseWriter, read_case}; +use core::f64; pub use descriptors::{CMAExportType, CMExportType, PhaseCM}; pub use flowmap::FlowMapDescriptor; pub use rawdata::{ @@ -14,7 +15,6 @@ pub use rawdata::{ ScalarFileHeader, ScalarValueType, }; pub use states::*; -use core::f64; use std::io; use thiserror::Error; pub use transitioner::*; @@ -64,32 +64,35 @@ pub fn get_transitioner(root: &str) -> Result(fmt: &T) -> f64 { let n_states = fmt.size(); - let mut min_all = f64::MAX; - + let mut min_all = f64::MAX; + for i_state in 0..n_states { - let state = fmt.get_at(i_state) + let state = fmt + .get_at(i_state) .expect("Transitioner error: n_state != real stored states"); - + if state.liquid.out_flows.len() != state.liquid.volumes.len() { panic!("Mismatched lengths between out_flows and volumes."); } - let min_i = state.liquid.out_flows.iter() + let min_i = state + .liquid + .out_flows + .iter() .zip(state.liquid.volumes.iter()) - .map(|(&f, &v)| f / v) - .filter(|&x| x.is_finite()) - .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Greater)) + .map(|(&f, &v)| f / v) + .filter(|&x| x.is_finite()) + .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Greater)) .expect("Should exist a minimum for the state"); - min_all = f64::min(min_all,min_i); + min_all = f64::min(min_all, min_i); } min_all diff --git a/cmtool/src/args/mod.rs b/cmtool/src/args/mod.rs index d510cbec..b3ca06a0 100644 --- a/cmtool/src/args/mod.rs +++ b/cmtool/src/args/mod.rs @@ -85,7 +85,6 @@ pub struct GenArgs { pub mode: AllModes, } - impl GenArgs { // #[cfg(debug_assertions)] // pub fn get() -> Self { diff --git a/cmtool/src/lib.rs b/cmtool/src/lib.rs index bf789e94..3a6d2ab1 100644 --- a/cmtool/src/lib.rs +++ b/cmtool/src/lib.rs @@ -16,4 +16,4 @@ pub enum CmtoolError { #[error("Cmtool: {0}")] Custom(String), -} \ No newline at end of file +} diff --git a/cmtool/src/main.rs b/cmtool/src/main.rs index 37a09939..d6b10e1c 100644 --- a/cmtool/src/main.rs +++ b/cmtool/src/main.rs @@ -9,9 +9,8 @@ use std::{env, path::Path}; mod args; use args::*; -fn out_or_default(out:Option)->String{ - out - .unwrap_or(format!("{}/../out/", env!("CARGO_MANIFEST_DIR"))) +fn out_or_default(out: Option) -> String { + out.unwrap_or(format!("{}/../out/", env!("CARGO_MANIFEST_DIR"))) } fn main() -> Result<(), CmtoolError> { @@ -37,8 +36,6 @@ fn auto_main(common: CommonArgs, autoargs: AutoArgs) -> Result<(), CmtoolError> .and_then(|s| s.to_str()) .unwrap(); // Converts OsStr to &str - - let root_dir = out_or_default(common.out); let case = cmtool_core::ensight_gold::case::Case::read(&autoargs.case_path)?; @@ -59,7 +56,7 @@ fn auto_main(common: CommonArgs, autoargs: AutoArgs) -> Result<(), CmtoolError> #[cfg(feature = "use_vtk")] handle.write_vtk(format!("{}/{}/cma_case.vtu", root_dir, stem)); - + return Ok(()); let p1 = "/home/benjamin/Documents/thesis/cfd-cma/sanofi_cfd/inputs/RESULTS.scl1"; let p2 = "/home/benjamin/Documents/thesis/cfd-cma/sanofi_cfd/inputs/RESULTS.scl2"; let p3 = "/home/benjamin/Documents/thesis/cfd-cma/sanofi_cfd/inputs/RESULTS.scl3"; diff --git a/examples/src/lib.rs b/examples/src/lib.rs index fb145070..9e08a8c9 100644 --- a/examples/src/lib.rs +++ b/examples/src/lib.rs @@ -1,4 +1 @@ pub mod map_generation; - - - diff --git a/examples/src/map_generation/case_0d1d.rs b/examples/src/map_generation/case_0d1d.rs index db65421b..0fd5af93 100644 --- a/examples/src/map_generation/case_0d1d.rs +++ b/examples/src/map_generation/case_0d1d.rs @@ -1,7 +1,5 @@ use cmtool_example::map_generation::generate; fn main() { - generate( - "examples/data/case_0d1d/reactors.xml" - ); + generate("examples/data/case_0d1d/reactors.xml"); } diff --git a/examples/src/map_generation/mod.rs b/examples/src/map_generation/mod.rs index f493e886..f86ff79e 100644 --- a/examples/src/map_generation/mod.rs +++ b/examples/src/map_generation/mod.rs @@ -2,12 +2,11 @@ use std::path::PathBuf; pub fn generate(reactor_input_file_name: &str) -> bool { let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../out/examples/"); - if let Err(msg)=cmtool_assemble::headless_generate(reactor_input_file_name, path){ - eprintln!("{}",msg); + if let Err(msg) = cmtool_assemble::headless_generate(reactor_input_file_name, path) { + eprintln!("{}", msg); return false; } return true; - } #[cfg(test)] diff --git a/examples/src/map_generation/neubauer.rs b/examples/src/map_generation/neubauer.rs index 71ffa81a..a45edc99 100644 --- a/examples/src/map_generation/neubauer.rs +++ b/examples/src/map_generation/neubauer.rs @@ -1,10 +1,6 @@ use cmtool_example::map_generation::generate; fn main() { - generate( - "examples/data/neubauer/reactors.xml", - ); - generate( - "examples/data/neubauer/reactors_batch.xml", - ); + generate("examples/data/neubauer/reactors.xml"); + generate("examples/data/neubauer/reactors_batch.xml"); } diff --git a/examples/src/map_generation/simple_0d.rs b/examples/src/map_generation/simple_0d.rs index 7432f7d9..b7e8f8e1 100644 --- a/examples/src/map_generation/simple_0d.rs +++ b/examples/src/map_generation/simple_0d.rs @@ -1,7 +1,5 @@ use cmtool_example::map_generation::generate; fn main() { - generate( - "examples/data/simple_0d/reactors.xml", - ); + generate("examples/data/simple_0d/reactors.xml"); } diff --git a/examples/src/map_generation/simple_1d.rs b/examples/src/map_generation/simple_1d.rs index f413acc3..336ec065 100644 --- a/examples/src/map_generation/simple_1d.rs +++ b/examples/src/map_generation/simple_1d.rs @@ -1,7 +1,5 @@ use cmtool_example::map_generation::generate; fn main() { - generate( - "examples/data/simple_1d/reactors.xml", - ); + generate("examples/data/simple_1d/reactors.xml"); } From aa934642905e91095c8252860e461dafa7e192eb Mon Sep 17 00:00:00 2001 From: bcasale Date: Fri, 28 Nov 2025 19:21:12 +0100 Subject: [PATCH 09/44] feat(generation): clean and add reactor from file xml descriptor --- cmtool-assemble/datamodel/reactors.xsd | 6 +++ cmtool-assemble/src/data.rs | 2 + cmtool-assemble/src/generators.rs | 12 +++--- cmtool-assemble/src/lib.rs | 21 +++++++--- cmtool-assemble/src/map_generation.rs | 18 ++++++--- .../src/parser/generated_domain.rs | 2 +- cmtool-assemble/src/parser/mod.rs | 2 + cmtool-assemble/src/parser/reactors.rs | 40 ++++++++++++++----- 8 files changed, 77 insertions(+), 26 deletions(-) diff --git a/cmtool-assemble/datamodel/reactors.xsd b/cmtool-assemble/datamodel/reactors.xsd index e51d7ac2..a3c03886 100644 --- a/cmtool-assemble/datamodel/reactors.xsd +++ b/cmtool-assemble/datamodel/reactors.xsd @@ -15,6 +15,11 @@ + + + + + @@ -61,6 +66,7 @@ + diff --git a/cmtool-assemble/src/data.rs b/cmtool-assemble/src/data.rs index 3d075c5c..6064906b 100644 --- a/cmtool-assemble/src/data.rs +++ b/cmtool-assemble/src/data.rs @@ -26,6 +26,7 @@ pub struct DomainInfo { pub compartment_cumsum: HashMap, pub total_number_compartment: usize, pub pfr_names: Vec, + pub cm_case_only:Option, } impl DomainInfo { pub fn get_relative_compartment_number( @@ -44,4 +45,5 @@ pub struct DomainData { pub connections: Option<[cmtool_data::RawDataFlux; 2]>, pub info: DomainInfo, pub feeds: Option, + pub case_path :String } diff --git a/cmtool-assemble/src/generators.rs b/cmtool-assemble/src/generators.rs index c38081be..a6e670cb 100644 --- a/cmtool-assemble/src/generators.rs +++ b/cmtool-assemble/src/generators.rs @@ -307,7 +307,7 @@ impl Generator { &self, dest: &str, connections: Option<[RawDataFlux; 2]>, - ) -> Result<(), CMError> { + ) -> Result { const MERGE_FOLDER_NAME: &str = "merged"; let liquid_phase = filter_phase(&self.raw_phase, PhaseCM::Liquid); let gasphase = filter_phase(&self.raw_phase, PhaseCM::Gas); @@ -328,10 +328,10 @@ impl Generator { let phase = Self::merge_phase(gasphase, gas_connection)?; Self::write_phase(&path, &mut case, phase, relative)?; } - - CMCaseJson::write_case(case.clone(), Path::new(&format!("{}/cma_case", dest)))?; + let path = format!("{}/cma_case", dest); + CMCaseJson::write_case(case.clone(), Path::new(&path))?; // cmtool_data::CCMCaseInfo::write_case(case, Path::new(&format!("{}/cma_case", dest)))?; - Ok(()) + Ok(path) } pub fn merge( @@ -339,7 +339,7 @@ impl Generator { dest: &str, ids: &[String], connections: Option<[RawDataFlux; 2]>, - ) -> Result<(), CMError> { + ) -> Result { let mut liquid_flows = Vec::with_capacity(ids.len()); let mut liquid_volumes = Vec::with_capacity(ids.len()); let mut gas_flows = Vec::with_capacity(ids.len()); @@ -412,7 +412,7 @@ impl Generator { CMCaseJson::write_case(case.clone(), Path::new(&format!("{}/jcma_case", dest)))?; let _ = cmtool_data::CCMCaseInfo::write_case(case, Path::new(&format!("{}/cma_case", dest))); - Ok(()) + Ok(path) } } diff --git a/cmtool-assemble/src/lib.rs b/cmtool-assemble/src/lib.rs index db1f7f9d..d10de6c7 100644 --- a/cmtool-assemble/src/lib.rs +++ b/cmtool-assemble/src/lib.rs @@ -12,6 +12,9 @@ use crate::parser::{generated_domain::RootElementType, get_root, parse_domain}; pub use data::{FeedFlow, ParsedFeeds}; use map_generation::generate_flowmap; use thiserror::Error; + +pub use cmtool_data::PhaseCM; //reexport to have easier dependency + #[derive(Error, Debug)] pub enum CMError { #[error("Cmtool encountered an unknown error. Please check the input and try again.")] @@ -58,16 +61,24 @@ impl Parser { Parser(root): Parser, root_dir: &str, ) -> Result { - let (domain, mb) = parse_domain(&root)?; - let root_dir = format!("{}", root_dir); - std::fs::create_dir_all(root_dir.clone())?; - generate_flowmap(&root_dir, &domain, &root.reactors, &mb)?; + let (mut domain, mb) = parse_domain(&root)?; + + let path = if let Some(cm_case) = &domain.info.cm_case_only { + cm_case.clone() + } else { + std::fs::create_dir_all(root_dir)?; + generate_flowmap(root_dir, &domain, &root.reactors, &mb)? + }; + domain.case_path = path; Ok(domain) } } -pub fn generate_domain(root_dir: &str, reactor_content: &str) -> Result { +pub fn generate_domain( + root_dir: &str, + reactor_content: &str, +) -> Result { let (_id, root) = Parser::start_parsing(reactor_content)?; let domain = Parser::continue_parsing(root, root_dir)?; diff --git a/cmtool-assemble/src/map_generation.rs b/cmtool-assemble/src/map_generation.rs index 67964862..eaca1595 100644 --- a/cmtool-assemble/src/map_generation.rs +++ b/cmtool-assemble/src/map_generation.rs @@ -114,6 +114,9 @@ fn generate_partial_flowmap( generated_domain::ReactorsTypeContent::Reactor3D(reactor3_dtype) => { todo!("{:?}", reactor3_dtype) } + generated_domain::ReactorsTypeContent::ReactorFromFile(_)=>{ + todo!("") + } } } Ok(ids) @@ -124,7 +127,7 @@ pub fn generate_flowmap( domain: &DomainData, reactors: &generated_domain::ReactorsType, mb: &PfrGlobalMassBalance, -) -> Result<(), CMError> { +) -> Result { let mut generator = Generator::new(); let save_intermediate = reactors.content.len() == 1; @@ -136,22 +139,27 @@ pub fn generate_flowmap( reactors, mb, )?; - + //TODO remove returning cm_path merge and merge_from_memory dont need to return it cause it is 'root' + //Same when len(id)==1 if _ids.len() > 1 { if save_intermediate { generator.merge(root, &_ids, domain.connections.clone())?; } else { generator.merge_from_memory(root, domain.connections.clone())?; } + Ok(root.to_owned()) } else if _ids.len() == 1 && save_intermediate { let case_path = format!("{}/{}/cma_case", root, _ids[0]); let prep = format!("./{}", _ids[0]); let case = CMCaseJson::read_case(std::path::Path::new(&case_path))?.prepend_path(&prep); - - CMCaseJson::write_case(case, std::path::Path::new(&format!("{}/cma_case", root)))?; + let path = format!("{}/cma_case", root); + CMCaseJson::write_case(case, std::path::Path::new(&path))?; + Ok(root.to_owned()) + }else{ + Err(CMError::Custom("TODO ".to_owned())) } - Ok(()) + } // fn parse_generate() diff --git a/cmtool-assemble/src/parser/generated_domain.rs b/cmtool-assemble/src/parser/generated_domain.rs index 28f6605c..feeb948a 100644 --- a/cmtool-assemble/src/parser/generated_domain.rs +++ b/cmtool-assemble/src/parser/generated_domain.rs @@ -1 +1 @@ -use serde :: { Deserialize , Serialize } ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct BaseReactorType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct ConnectionsType { # [serde (default , rename = "Flux")] pub flux : Vec < FluxType > , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct DimensionType { # [serde (default , rename = "@unit")] pub unit : Option < :: std :: string :: String > , # [serde (rename = "#text")] pub content : :: core :: primitive :: f32 , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct FeedFluxType { # [serde (rename = "@phase")] pub phase : :: std :: string :: String , # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "Source")] pub source : NodeType , # [serde (rename = "Target")] pub target : NodeType , # [serde (rename = "Value")] pub value : DimensionType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct FeedsType { # [serde (default , rename = "Flux")] pub flux : Vec < FeedFluxType > , } pub type FlowType = DimensionType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct FluxType { # [serde (rename = "@phase")] pub phase : :: std :: string :: String , # [serde (rename = "Source")] pub source : NodeType , # [serde (rename = "Target")] pub target : NodeType , # [serde (rename = "Value")] pub value : DimensionType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub enum GeneralSizeType { # [serde (rename = "Volume")] Volume (DimensionType) , # [serde (rename = "Dimension")] Dimension (SizeCylindricalType) , } pub type LengthType = DimensionType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct NodeType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "@compartment_id")] pub compartment_id : :: core :: primitive :: usize , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct Reactor0DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct Reactor1DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , # [serde (rename = "Compartments")] pub compartments : :: core :: primitive :: usize , # [serde (rename = "Dispersion")] pub dispersion : DimensionType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct Reactor3DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , # [serde (rename = "file")] pub file : :: std :: string :: String , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct ReactorsType { # [serde (rename = "#content")] pub content : Vec < ReactorsTypeContent > , } # [derive (Debug , Clone , Serialize , Deserialize)] pub enum ReactorsTypeContent { # [serde (rename = "Reactor0D")] Reactor0D (Reactor0DType) , # [serde (rename = "Reactor1D")] Reactor1D (Reactor1DType) , # [serde (rename = "Reactor3D")] Reactor3D (Reactor3DType) , } pub type Root = RootElementType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct RootElementType { # [serde (rename = "@run_id")] pub run_id : :: std :: string :: String , # [serde (rename = "@version")] pub version : :: core :: primitive :: i32 , # [serde (rename = "Reactors")] pub reactors : ReactorsType , # [serde (default , rename = "Connections")] pub connections : Option < ConnectionsType > , # [serde (default , rename = "Feeds")] pub feeds : Option < FeedsType > , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct SizeCylindricalType { # [serde (rename = "Diameter")] pub diameter : DimensionType , # [serde (rename = "Length")] pub length : DimensionType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub enum SizeSpecificationType { # [serde (rename = "Volume")] Volume (DimensionType) , # [serde (rename = "CylindricalDimensions")] CylindricalDimensions (SizeSpecificationCylindricalDimensionsElementType) , } pub type TimeType = DimensionType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct VolumeFractionType { # [serde (default , rename = "@phase")] pub phase : Option < :: std :: string :: String > , # [serde (rename = "#text")] pub content : :: core :: primitive :: f32 , } pub type VolumeType = DimensionType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct SizeSpecificationCylindricalDimensionsElementType { # [serde (rename = "Diameter")] pub diameter : DimensionType , # [serde (rename = "Length")] pub length : DimensionType , } pub mod xs { use serde :: { Deserialize , Serialize } ; # [derive (Debug , Clone , Serialize , Deserialize , Default)] pub struct EntitiesType (pub Vec < :: std :: string :: String >) ; pub type EntityType = EntitiesType ; pub type IdType = :: std :: string :: String ; pub type IdrefType = :: std :: string :: String ; pub type IdrefsType = EntitiesType ; pub type NcNameType = :: std :: string :: String ; pub type NmtokenType = :: std :: string :: String ; pub type NmtokensType = EntitiesType ; pub type NotationType = :: std :: string :: String ; pub type NameType = :: std :: string :: String ; pub type QNameType = :: std :: string :: String ; pub type AnySimpleType = :: std :: string :: String ; pub type AnyUriType = :: std :: string :: String ; pub type Base64BinaryType = :: std :: string :: String ; pub type BooleanType = :: core :: primitive :: bool ; pub type ByteType = :: core :: primitive :: i8 ; pub type DateType = :: std :: string :: String ; pub type DateTimeType = :: std :: string :: String ; pub type DecimalType = :: core :: primitive :: f64 ; pub type DoubleType = :: core :: primitive :: f64 ; pub type DurationType = :: std :: string :: String ; pub type FloatType = :: core :: primitive :: f32 ; pub type GDayType = :: std :: string :: String ; pub type GMonthType = :: std :: string :: String ; pub type GMonthDayType = :: std :: string :: String ; pub type GYearType = :: std :: string :: String ; pub type GYearMonthType = :: std :: string :: String ; pub type HexBinaryType = :: std :: string :: String ; pub type IntType = :: core :: primitive :: i32 ; pub type IntegerType = :: core :: primitive :: i32 ; pub type LanguageType = :: std :: string :: String ; pub type LongType = :: core :: primitive :: i64 ; pub type NegativeIntegerType = :: core :: primitive :: isize ; pub type NonNegativeIntegerType = :: core :: primitive :: usize ; pub type NonPositiveIntegerType = :: core :: primitive :: isize ; pub type NormalizedStringType = :: std :: string :: String ; pub type PositiveIntegerType = :: core :: primitive :: usize ; pub type ShortType = :: core :: primitive :: i16 ; pub type StringType = :: std :: string :: String ; pub type TimeType = :: std :: string :: String ; pub type TokenType = :: std :: string :: String ; pub type UnsignedByteType = :: core :: primitive :: u8 ; pub type UnsignedIntType = :: core :: primitive :: u32 ; pub type UnsignedLongType = :: core :: primitive :: u64 ; pub type UnsignedShortType = :: core :: primitive :: u16 ; } \ No newline at end of file +use serde :: { Deserialize , Serialize } ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct BaseReactorType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct ConnectionsType { # [serde (default , rename = "Flux")] pub flux : Vec < FluxType > , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct DimensionType { # [serde (default , rename = "@unit")] pub unit : Option < :: std :: string :: String > , # [serde (rename = "#text")] pub content : :: core :: primitive :: f32 , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct FeedFluxType { # [serde (rename = "@phase")] pub phase : :: std :: string :: String , # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "Source")] pub source : NodeType , # [serde (rename = "Target")] pub target : NodeType , # [serde (rename = "Value")] pub value : DimensionType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct FeedsType { # [serde (default , rename = "Flux")] pub flux : Vec < FeedFluxType > , } pub type FlowType = DimensionType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct FluxType { # [serde (rename = "@phase")] pub phase : :: std :: string :: String , # [serde (rename = "Source")] pub source : NodeType , # [serde (rename = "Target")] pub target : NodeType , # [serde (rename = "Value")] pub value : DimensionType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub enum GeneralSizeType { # [serde (rename = "Volume")] Volume (DimensionType) , # [serde (rename = "Dimension")] Dimension (SizeCylindricalType) , } pub type LengthType = DimensionType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct NodeType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "@compartment_id")] pub compartment_id : :: core :: primitive :: usize , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct Reactor0DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct Reactor1DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , # [serde (rename = "Compartments")] pub compartments : :: core :: primitive :: usize , # [serde (rename = "Dispersion")] pub dispersion : DimensionType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct Reactor3DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , # [serde (rename = "file")] pub file : :: std :: string :: String , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct ReactorFromFileType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "@Path")] pub path : :: std :: string :: String , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct ReactorsType { # [serde (rename = "#content")] pub content : Vec < ReactorsTypeContent > , } # [derive (Debug , Clone , Serialize , Deserialize)] pub enum ReactorsTypeContent { # [serde (rename = "Reactor0D")] Reactor0D (Reactor0DType) , # [serde (rename = "Reactor1D")] Reactor1D (Reactor1DType) , # [serde (rename = "Reactor3D")] Reactor3D (Reactor3DType) , # [serde (rename = "ReactorFromFile")] ReactorFromFile (ReactorFromFileType) , } pub type Root = RootElementType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct RootElementType { # [serde (rename = "@run_id")] pub run_id : :: std :: string :: String , # [serde (rename = "@version")] pub version : :: core :: primitive :: i32 , # [serde (rename = "Reactors")] pub reactors : ReactorsType , # [serde (default , rename = "Connections")] pub connections : Option < ConnectionsType > , # [serde (default , rename = "Feeds")] pub feeds : Option < FeedsType > , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct SizeCylindricalType { # [serde (rename = "Diameter")] pub diameter : DimensionType , # [serde (rename = "Length")] pub length : DimensionType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub enum SizeSpecificationType { # [serde (rename = "Volume")] Volume (DimensionType) , # [serde (rename = "CylindricalDimensions")] CylindricalDimensions (SizeSpecificationCylindricalDimensionsElementType) , } pub type TimeType = DimensionType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct VolumeFractionType { # [serde (default , rename = "@phase")] pub phase : Option < :: std :: string :: String > , # [serde (rename = "#text")] pub content : :: core :: primitive :: f32 , } pub type VolumeType = DimensionType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct SizeSpecificationCylindricalDimensionsElementType { # [serde (rename = "Diameter")] pub diameter : DimensionType , # [serde (rename = "Length")] pub length : DimensionType , } pub mod xs { use serde :: { Deserialize , Serialize } ; # [derive (Debug , Clone , Serialize , Deserialize , Default)] pub struct EntitiesType (pub Vec < :: std :: string :: String >) ; pub type EntityType = EntitiesType ; pub type IdType = :: std :: string :: String ; pub type IdrefType = :: std :: string :: String ; pub type IdrefsType = EntitiesType ; pub type NcNameType = :: std :: string :: String ; pub type NmtokenType = :: std :: string :: String ; pub type NmtokensType = EntitiesType ; pub type NotationType = :: std :: string :: String ; pub type NameType = :: std :: string :: String ; pub type QNameType = :: std :: string :: String ; pub type AnySimpleType = :: std :: string :: String ; pub type AnyUriType = :: std :: string :: String ; pub type Base64BinaryType = :: std :: string :: String ; pub type BooleanType = :: core :: primitive :: bool ; pub type ByteType = :: core :: primitive :: i8 ; pub type DateType = :: std :: string :: String ; pub type DateTimeType = :: std :: string :: String ; pub type DecimalType = :: core :: primitive :: f64 ; pub type DoubleType = :: core :: primitive :: f64 ; pub type DurationType = :: std :: string :: String ; pub type FloatType = :: core :: primitive :: f32 ; pub type GDayType = :: std :: string :: String ; pub type GMonthType = :: std :: string :: String ; pub type GMonthDayType = :: std :: string :: String ; pub type GYearType = :: std :: string :: String ; pub type GYearMonthType = :: std :: string :: String ; pub type HexBinaryType = :: std :: string :: String ; pub type IntType = :: core :: primitive :: i32 ; pub type IntegerType = :: core :: primitive :: i32 ; pub type LanguageType = :: std :: string :: String ; pub type LongType = :: core :: primitive :: i64 ; pub type NegativeIntegerType = :: core :: primitive :: isize ; pub type NonNegativeIntegerType = :: core :: primitive :: usize ; pub type NonPositiveIntegerType = :: core :: primitive :: isize ; pub type NormalizedStringType = :: std :: string :: String ; pub type PositiveIntegerType = :: core :: primitive :: usize ; pub type ShortType = :: core :: primitive :: i16 ; pub type StringType = :: std :: string :: String ; pub type TimeType = :: std :: string :: String ; pub type TokenType = :: std :: string :: String ; pub type UnsignedByteType = :: core :: primitive :: u8 ; pub type UnsignedIntType = :: core :: primitive :: u32 ; pub type UnsignedLongType = :: core :: primitive :: u64 ; pub type UnsignedShortType = :: core :: primitive :: u16 ; } \ No newline at end of file diff --git a/cmtool-assemble/src/parser/mod.rs b/cmtool-assemble/src/parser/mod.rs index 85fae659..a6f77d76 100644 --- a/cmtool-assemble/src/parser/mod.rs +++ b/cmtool-assemble/src/parser/mod.rs @@ -14,6 +14,7 @@ pub(super) use pfr_mb::PfrGlobalMassBalance; pub fn parse_domain( root: &generated_domain::RootElementType, ) -> Result<(DomainData, PfrGlobalMassBalance), CMError> { + let info = parse_reactor(&root.reactors)?; let mut mass_balance = PfrGlobalMassBalance::new(&info.pfr_names); @@ -34,6 +35,7 @@ pub fn parse_domain( connections: raw_connections, info, feeds: pfeeds, + case_path:String::new() }, mass_balance, )) diff --git a/cmtool-assemble/src/parser/reactors.rs b/cmtool-assemble/src/parser/reactors.rs index a9842906..97549afe 100644 --- a/cmtool-assemble/src/parser/reactors.rs +++ b/cmtool-assemble/src/parser/reactors.rs @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-or-later use std::collections::HashMap; +use std::path::Path; +use std::path::PathBuf; use super::PfrGlobalMassBalance; use super::generated_domain; @@ -163,30 +165,50 @@ pub fn parse_feed( } pub fn parse_reactor(reactors: &generated_domain::ReactorsType) -> Result { - let mut parseinfo = DomainInfo::default(); - + let mut domain_info = DomainInfo::default(); + let mut cm_case_only = None; let mut in_place_cumsum = 0; for reactor in &reactors.content { match reactor { generated_domain::ReactorsTypeContent::Reactor0D(reactor0_dtype) => { - parseinfo + domain_info .compartment_cumsum .insert(reactor0_dtype.id.clone(), in_place_cumsum); - parseinfo.total_number_compartment += 1; + domain_info.total_number_compartment += 1; in_place_cumsum += 1; } generated_domain::ReactorsTypeContent::Reactor1D(current_pfr) => { - parseinfo + domain_info .compartment_cumsum .insert(current_pfr.id.clone(), in_place_cumsum); - parseinfo.total_number_compartment += current_pfr.compartments; - parseinfo.pfr_names.push(current_pfr.id.clone()); - in_place_cumsum += current_pfr.compartments; + let n_c = current_pfr.compartments; + domain_info.total_number_compartment += n_c; + domain_info.pfr_names.push(current_pfr.id.clone()); + in_place_cumsum += n_c; + } + generated_domain::ReactorsTypeContent::ReactorFromFile(reactor_from_file)=>{ + let path = PathBuf::from(reactor_from_file.path.clone()); + let case = cmtool_data::read_case(path.as_path())?; + // case.n_compartment() + domain_info.compartment_cumsum.insert(reactor_from_file.id.clone(),in_place_cumsum); + let n_c = case.n_compartment() as usize; + domain_info.total_number_compartment+=n_c; + in_place_cumsum+=n_c; + + if cm_case_only.is_none() + { + cm_case_only = Some(reactor_from_file.path.clone()); + } else{ + todo!("Existing flowmap merge") + } + + } generated_domain::ReactorsTypeContent::Reactor3D(reactor3_dtype) => { todo!("{:?}", reactor3_dtype) } } } - Ok(parseinfo) + domain_info.cm_case_only = cm_case_only; + Ok(domain_info) } From 06d6c4fc13b5dcde9586a8766383468767d58f20 Mon Sep 17 00:00:00 2001 From: Casale Date: Sun, 30 Nov 2025 10:32:52 +0100 Subject: [PATCH 10/44] fix: reactor from file scheme and feed parsing --- cmtool-assemble/datamodel/reactors.xsd | 5 +++- cmtool-assemble/src/data.rs | 1 + .../src/parser/generated_domain.rs | 2 +- cmtool-assemble/src/parser/mod.rs | 8 +++++- cmtool-assemble/src/parser/reactors.rs | 25 +++++++++++++------ cmtool-data/src/case.rs | 8 ++++++ 6 files changed, 39 insertions(+), 10 deletions(-) diff --git a/cmtool-assemble/datamodel/reactors.xsd b/cmtool-assemble/datamodel/reactors.xsd index a3c03886..cc41e2fa 100644 --- a/cmtool-assemble/datamodel/reactors.xsd +++ b/cmtool-assemble/datamodel/reactors.xsd @@ -17,7 +17,10 @@ - + + + + diff --git a/cmtool-assemble/src/data.rs b/cmtool-assemble/src/data.rs index 6064906b..27698715 100644 --- a/cmtool-assemble/src/data.rs +++ b/cmtool-assemble/src/data.rs @@ -27,6 +27,7 @@ pub struct DomainInfo { pub total_number_compartment: usize, pub pfr_names: Vec, pub cm_case_only:Option, + pub is_two_phase_flow:bool } impl DomainInfo { pub fn get_relative_compartment_number( diff --git a/cmtool-assemble/src/parser/generated_domain.rs b/cmtool-assemble/src/parser/generated_domain.rs index feeb948a..89743883 100644 --- a/cmtool-assemble/src/parser/generated_domain.rs +++ b/cmtool-assemble/src/parser/generated_domain.rs @@ -1 +1 @@ -use serde :: { Deserialize , Serialize } ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct BaseReactorType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct ConnectionsType { # [serde (default , rename = "Flux")] pub flux : Vec < FluxType > , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct DimensionType { # [serde (default , rename = "@unit")] pub unit : Option < :: std :: string :: String > , # [serde (rename = "#text")] pub content : :: core :: primitive :: f32 , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct FeedFluxType { # [serde (rename = "@phase")] pub phase : :: std :: string :: String , # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "Source")] pub source : NodeType , # [serde (rename = "Target")] pub target : NodeType , # [serde (rename = "Value")] pub value : DimensionType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct FeedsType { # [serde (default , rename = "Flux")] pub flux : Vec < FeedFluxType > , } pub type FlowType = DimensionType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct FluxType { # [serde (rename = "@phase")] pub phase : :: std :: string :: String , # [serde (rename = "Source")] pub source : NodeType , # [serde (rename = "Target")] pub target : NodeType , # [serde (rename = "Value")] pub value : DimensionType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub enum GeneralSizeType { # [serde (rename = "Volume")] Volume (DimensionType) , # [serde (rename = "Dimension")] Dimension (SizeCylindricalType) , } pub type LengthType = DimensionType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct NodeType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "@compartment_id")] pub compartment_id : :: core :: primitive :: usize , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct Reactor0DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct Reactor1DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , # [serde (rename = "Compartments")] pub compartments : :: core :: primitive :: usize , # [serde (rename = "Dispersion")] pub dispersion : DimensionType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct Reactor3DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , # [serde (rename = "file")] pub file : :: std :: string :: String , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct ReactorFromFileType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "@Path")] pub path : :: std :: string :: String , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct ReactorsType { # [serde (rename = "#content")] pub content : Vec < ReactorsTypeContent > , } # [derive (Debug , Clone , Serialize , Deserialize)] pub enum ReactorsTypeContent { # [serde (rename = "Reactor0D")] Reactor0D (Reactor0DType) , # [serde (rename = "Reactor1D")] Reactor1D (Reactor1DType) , # [serde (rename = "Reactor3D")] Reactor3D (Reactor3DType) , # [serde (rename = "ReactorFromFile")] ReactorFromFile (ReactorFromFileType) , } pub type Root = RootElementType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct RootElementType { # [serde (rename = "@run_id")] pub run_id : :: std :: string :: String , # [serde (rename = "@version")] pub version : :: core :: primitive :: i32 , # [serde (rename = "Reactors")] pub reactors : ReactorsType , # [serde (default , rename = "Connections")] pub connections : Option < ConnectionsType > , # [serde (default , rename = "Feeds")] pub feeds : Option < FeedsType > , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct SizeCylindricalType { # [serde (rename = "Diameter")] pub diameter : DimensionType , # [serde (rename = "Length")] pub length : DimensionType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub enum SizeSpecificationType { # [serde (rename = "Volume")] Volume (DimensionType) , # [serde (rename = "CylindricalDimensions")] CylindricalDimensions (SizeSpecificationCylindricalDimensionsElementType) , } pub type TimeType = DimensionType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct VolumeFractionType { # [serde (default , rename = "@phase")] pub phase : Option < :: std :: string :: String > , # [serde (rename = "#text")] pub content : :: core :: primitive :: f32 , } pub type VolumeType = DimensionType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct SizeSpecificationCylindricalDimensionsElementType { # [serde (rename = "Diameter")] pub diameter : DimensionType , # [serde (rename = "Length")] pub length : DimensionType , } pub mod xs { use serde :: { Deserialize , Serialize } ; # [derive (Debug , Clone , Serialize , Deserialize , Default)] pub struct EntitiesType (pub Vec < :: std :: string :: String >) ; pub type EntityType = EntitiesType ; pub type IdType = :: std :: string :: String ; pub type IdrefType = :: std :: string :: String ; pub type IdrefsType = EntitiesType ; pub type NcNameType = :: std :: string :: String ; pub type NmtokenType = :: std :: string :: String ; pub type NmtokensType = EntitiesType ; pub type NotationType = :: std :: string :: String ; pub type NameType = :: std :: string :: String ; pub type QNameType = :: std :: string :: String ; pub type AnySimpleType = :: std :: string :: String ; pub type AnyUriType = :: std :: string :: String ; pub type Base64BinaryType = :: std :: string :: String ; pub type BooleanType = :: core :: primitive :: bool ; pub type ByteType = :: core :: primitive :: i8 ; pub type DateType = :: std :: string :: String ; pub type DateTimeType = :: std :: string :: String ; pub type DecimalType = :: core :: primitive :: f64 ; pub type DoubleType = :: core :: primitive :: f64 ; pub type DurationType = :: std :: string :: String ; pub type FloatType = :: core :: primitive :: f32 ; pub type GDayType = :: std :: string :: String ; pub type GMonthType = :: std :: string :: String ; pub type GMonthDayType = :: std :: string :: String ; pub type GYearType = :: std :: string :: String ; pub type GYearMonthType = :: std :: string :: String ; pub type HexBinaryType = :: std :: string :: String ; pub type IntType = :: core :: primitive :: i32 ; pub type IntegerType = :: core :: primitive :: i32 ; pub type LanguageType = :: std :: string :: String ; pub type LongType = :: core :: primitive :: i64 ; pub type NegativeIntegerType = :: core :: primitive :: isize ; pub type NonNegativeIntegerType = :: core :: primitive :: usize ; pub type NonPositiveIntegerType = :: core :: primitive :: isize ; pub type NormalizedStringType = :: std :: string :: String ; pub type PositiveIntegerType = :: core :: primitive :: usize ; pub type ShortType = :: core :: primitive :: i16 ; pub type StringType = :: std :: string :: String ; pub type TimeType = :: std :: string :: String ; pub type TokenType = :: std :: string :: String ; pub type UnsignedByteType = :: core :: primitive :: u8 ; pub type UnsignedIntType = :: core :: primitive :: u32 ; pub type UnsignedLongType = :: core :: primitive :: u64 ; pub type UnsignedShortType = :: core :: primitive :: u16 ; } \ No newline at end of file +use serde :: { Deserialize , Serialize } ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct BaseReactorType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct ConnectionsType { # [serde (default , rename = "Flux")] pub flux : Vec < FluxType > , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct DimensionType { # [serde (default , rename = "@unit")] pub unit : Option < :: std :: string :: String > , # [serde (rename = "#text")] pub content : :: core :: primitive :: f32 , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct FeedFluxType { # [serde (rename = "@phase")] pub phase : :: std :: string :: String , # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "Source")] pub source : NodeType , # [serde (rename = "Target")] pub target : NodeType , # [serde (rename = "Value")] pub value : DimensionType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct FeedsType { # [serde (default , rename = "Flux")] pub flux : Vec < FeedFluxType > , } pub type FlowType = DimensionType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct FluxType { # [serde (rename = "@phase")] pub phase : :: std :: string :: String , # [serde (rename = "Source")] pub source : NodeType , # [serde (rename = "Target")] pub target : NodeType , # [serde (rename = "Value")] pub value : DimensionType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub enum GeneralSizeType { # [serde (rename = "Volume")] Volume (DimensionType) , # [serde (rename = "Dimension")] Dimension (SizeCylindricalType) , } pub type LengthType = DimensionType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct NodeType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "@compartment_id")] pub compartment_id : :: core :: primitive :: usize , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct Reactor0DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct Reactor1DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , # [serde (rename = "Compartments")] pub compartments : :: core :: primitive :: usize , # [serde (rename = "Dispersion")] pub dispersion : DimensionType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct Reactor3DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , # [serde (rename = "file")] pub file : :: std :: string :: String , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct ReactorFromFileType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "Path")] pub path : :: std :: string :: String , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct ReactorsType { # [serde (rename = "#content")] pub content : Vec < ReactorsTypeContent > , } # [derive (Debug , Clone , Serialize , Deserialize)] pub enum ReactorsTypeContent { # [serde (rename = "Reactor0D")] Reactor0D (Reactor0DType) , # [serde (rename = "Reactor1D")] Reactor1D (Reactor1DType) , # [serde (rename = "Reactor3D")] Reactor3D (Reactor3DType) , # [serde (rename = "ReactorFromFile")] ReactorFromFile (ReactorFromFileType) , } pub type Root = RootElementType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct RootElementType { # [serde (rename = "@run_id")] pub run_id : :: std :: string :: String , # [serde (rename = "@version")] pub version : :: core :: primitive :: i32 , # [serde (rename = "Reactors")] pub reactors : ReactorsType , # [serde (default , rename = "Connections")] pub connections : Option < ConnectionsType > , # [serde (default , rename = "Feeds")] pub feeds : Option < FeedsType > , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct SizeCylindricalType { # [serde (rename = "Diameter")] pub diameter : DimensionType , # [serde (rename = "Length")] pub length : DimensionType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub enum SizeSpecificationType { # [serde (rename = "Volume")] Volume (DimensionType) , # [serde (rename = "CylindricalDimensions")] CylindricalDimensions (SizeSpecificationCylindricalDimensionsElementType) , } pub type TimeType = DimensionType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct VolumeFractionType { # [serde (default , rename = "@phase")] pub phase : Option < :: std :: string :: String > , # [serde (rename = "#text")] pub content : :: core :: primitive :: f32 , } pub type VolumeType = DimensionType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct SizeSpecificationCylindricalDimensionsElementType { # [serde (rename = "Diameter")] pub diameter : DimensionType , # [serde (rename = "Length")] pub length : DimensionType , } pub mod xs { use serde :: { Deserialize , Serialize } ; # [derive (Debug , Clone , Serialize , Deserialize , Default)] pub struct EntitiesType (pub Vec < :: std :: string :: String >) ; pub type EntityType = EntitiesType ; pub type IdType = :: std :: string :: String ; pub type IdrefType = :: std :: string :: String ; pub type IdrefsType = EntitiesType ; pub type NcNameType = :: std :: string :: String ; pub type NmtokenType = :: std :: string :: String ; pub type NmtokensType = EntitiesType ; pub type NotationType = :: std :: string :: String ; pub type NameType = :: std :: string :: String ; pub type QNameType = :: std :: string :: String ; pub type AnySimpleType = :: std :: string :: String ; pub type AnyUriType = :: std :: string :: String ; pub type Base64BinaryType = :: std :: string :: String ; pub type BooleanType = :: core :: primitive :: bool ; pub type ByteType = :: core :: primitive :: i8 ; pub type DateType = :: std :: string :: String ; pub type DateTimeType = :: std :: string :: String ; pub type DecimalType = :: core :: primitive :: f64 ; pub type DoubleType = :: core :: primitive :: f64 ; pub type DurationType = :: std :: string :: String ; pub type FloatType = :: core :: primitive :: f32 ; pub type GDayType = :: std :: string :: String ; pub type GMonthType = :: std :: string :: String ; pub type GMonthDayType = :: std :: string :: String ; pub type GYearType = :: std :: string :: String ; pub type GYearMonthType = :: std :: string :: String ; pub type HexBinaryType = :: std :: string :: String ; pub type IntType = :: core :: primitive :: i32 ; pub type IntegerType = :: core :: primitive :: i32 ; pub type LanguageType = :: std :: string :: String ; pub type LongType = :: core :: primitive :: i64 ; pub type NegativeIntegerType = :: core :: primitive :: isize ; pub type NonNegativeIntegerType = :: core :: primitive :: usize ; pub type NonPositiveIntegerType = :: core :: primitive :: isize ; pub type NormalizedStringType = :: std :: string :: String ; pub type PositiveIntegerType = :: core :: primitive :: usize ; pub type ShortType = :: core :: primitive :: i16 ; pub type StringType = :: std :: string :: String ; pub type TimeType = :: std :: string :: String ; pub type TokenType = :: std :: string :: String ; pub type UnsignedByteType = :: core :: primitive :: u8 ; pub type UnsignedIntType = :: core :: primitive :: u32 ; pub type UnsignedLongType = :: core :: primitive :: u64 ; pub type UnsignedShortType = :: core :: primitive :: u16 ; } \ No newline at end of file diff --git a/cmtool-assemble/src/parser/mod.rs b/cmtool-assemble/src/parser/mod.rs index a6f77d76..d4990dac 100644 --- a/cmtool-assemble/src/parser/mod.rs +++ b/cmtool-assemble/src/parser/mod.rs @@ -15,8 +15,14 @@ pub fn parse_domain( root: &generated_domain::RootElementType, ) -> Result<(DomainData, PfrGlobalMassBalance), CMError> { + if root.reactors.content.is_empty() + { + return Err(CMError::Parse(serde_xml_rs::Error::Custom("At least one reactor required".to_owned()))); + } + let info = parse_reactor(&root.reactors)?; + let mut mass_balance = PfrGlobalMassBalance::new(&info.pfr_names); let raw_connections = root @@ -26,7 +32,7 @@ pub fn parse_domain( let mut pfeeds = None; if let Some(feeds) = &root.feeds { - pfeeds = Some(parse_feed(&info, feeds, &mut mass_balance)); //TODO + pfeeds = parse_feed(&info, feeds, &mut mass_balance); } mass_balance.validate()?; diff --git a/cmtool-assemble/src/parser/reactors.rs b/cmtool-assemble/src/parser/reactors.rs index 97549afe..3b0acb7b 100644 --- a/cmtool-assemble/src/parser/reactors.rs +++ b/cmtool-assemble/src/parser/reactors.rs @@ -151,23 +151,27 @@ pub fn parse_feed( info: &DomainInfo, feeds: &generated_domain::FeedsType, mass_balance: &mut PfrGlobalMassBalance, -) -> ParsedFeeds { +) -> Option { let (liquid_feeds, gas_feeds): (Vec<_>, Vec<_>) = feeds .flux .iter() .partition(|f| f.phase == *"liquid") .clone(); - - ParsedFeeds { + if liquid_feeds.is_empty() && gas_feeds.is_empty() + { + return None; + } + Some(ParsedFeeds { liq: parse_feed_phase(info, &liquid_feeds, PhaseCM::Liquid, mass_balance), gas: parse_feed_phase(info, &gas_feeds, PhaseCM::Gas, mass_balance), - } + }) } pub fn parse_reactor(reactors: &generated_domain::ReactorsType) -> Result { let mut domain_info = DomainInfo::default(); let mut cm_case_only = None; let mut in_place_cumsum = 0; + for reactor in &reactors.content { match reactor { generated_domain::ReactorsTypeContent::Reactor0D(reactor0_dtype) => { @@ -176,6 +180,7 @@ pub fn parse_reactor(reactors: &generated_domain::ReactorsType) -> Result { domain_info @@ -183,25 +188,31 @@ pub fn parse_reactor(reactors: &generated_domain::ReactorsType) -> Result{ - let path = PathBuf::from(reactor_from_file.path.clone()); + + let path = format!("{}/cma_case",reactor_from_file.path); + + + let path = PathBuf::from(path); let case = cmtool_data::read_case(path.as_path())?; // case.n_compartment() domain_info.compartment_cumsum.insert(reactor_from_file.id.clone(),in_place_cumsum); let n_c = case.n_compartment() as usize; + domain_info.is_two_phase_flow = case.is_two_phase_flow(); domain_info.total_number_compartment+=n_c; in_place_cumsum+=n_c; - + if cm_case_only.is_none() { cm_case_only = Some(reactor_from_file.path.clone()); } else{ todo!("Existing flowmap merge") } - + println!("{:?}",cm_case_only); } generated_domain::ReactorsTypeContent::Reactor3D(reactor3_dtype) => { diff --git a/cmtool-data/src/case.rs b/cmtool-data/src/case.rs index b8764961..e94d52a3 100644 --- a/cmtool-data/src/case.rs +++ b/cmtool-data/src/case.rs @@ -58,6 +58,10 @@ impl std::fmt::Display for CMCase { impl CMCase { pub fn n_compartment(&self) -> u32 { + if self.n_div.iter().find(|e| **e==0).is_some() + { + return 1; + } self.n_div.iter().product() } @@ -65,6 +69,10 @@ impl CMCase { self.is_reursive = !self.is_reursive; } + pub fn is_two_phase_flow(&self)->bool{ + self.paths.contains_key(&CMAExportType::GasVolume) + } + pub fn add(&mut self, stype: CMAExportType, relative_path: &str) { self.paths.insert(stype, relative_path.to_string()); } From 1ab61bec82b71331505fa0b5f746b659f5212daa Mon Sep 17 00:00:00 2001 From: bcasale Date: Fri, 5 Dec 2025 19:16:27 +0100 Subject: [PATCH 11/44] feat(cxx): add has_misc to check if existing key --- cmtool-cxx/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmtool-cxx/src/lib.rs b/cmtool-cxx/src/lib.rs index 439da0d5..ee2baa2a 100644 --- a/cmtool-cxx/src/lib.rs +++ b/cmtool-cxx/src/lib.rs @@ -51,6 +51,8 @@ mod ffi { unsafe fn get_misc<'a>(self: &'a IterationStateWrapper, key: &str) -> &'a [f64]; + fn has_misc(self: &IterationStateWrapper, key: &str) -> bool; + fn flat_neighobrs(self: &IterationStateWrapper) -> &[usize]; fn n_compartments(self: &IterationStateWrapper) -> usize; @@ -173,6 +175,9 @@ impl IterationStateWrapper { None => Box::new(HydroStateWrapper(null())), } } + fn has_misc(self: &IterationStateWrapper, key: &str) -> bool { + unsafe { &*self.0 }.get(key).is_some() + } fn get_misc(self: &IterationStateWrapper, key: &str) -> &[f64] { unsafe { &*self.0 }.get(key).unwrap() From 3166c7bf1ab53dfac6e74b169024987211c3b9d7 Mon Sep 17 00:00:00 2001 From: bcasale Date: Mon, 8 Dec 2025 16:12:21 +0100 Subject: [PATCH 12/44] feat(python): improve bindings transition and rawscalar --- cmtool-data/src/rawdata.rs | 15 ++++-- cmtool-python/src/case.rs | 3 ++ cmtool-python/src/lib.rs | 2 +- cmtool-python/src/rd.rs | 59 +++++++++++++++++++---- cmtool-python/src/transitionner.rs | 76 +++++++++++++++++++++++------- 5 files changed, 122 insertions(+), 33 deletions(-) diff --git a/cmtool-data/src/rawdata.rs b/cmtool-data/src/rawdata.rs index 77772db7..4e7219ce 100644 --- a/cmtool-data/src/rawdata.rs +++ b/cmtool-data/src/rawdata.rs @@ -193,11 +193,18 @@ impl RawDataScalar { impl From> for RawDataScalar { fn from(value: Vec) -> Self { + value.as_slice().into() + } +} + +impl From<&[f64]> for RawDataScalar { + fn from(value: &[f64]) -> Self { + let len: u32 = value.len().try_into().unwrap_or_else(|_| { + panic!("Array length is too large to convert into u32"); + }); Self { - header: ScalarFileHeader { - n_zone: value.len().try_into().unwrap(), - }, - values: value.into_iter().map(|i| i.into()).collect(), + header: ScalarFileHeader { n_zone: len }, + values: value.iter().copied().map(Into::into).collect(), } } } diff --git a/cmtool-python/src/case.rs b/cmtool-python/src/case.rs index fd5237a1..253eab37 100644 --- a/cmtool-python/src/case.rs +++ b/cmtool-python/src/case.rs @@ -48,6 +48,9 @@ impl CMCaseWrapper { cmtool_data::CMCaseJson::write_case(c, p).map_err(PythonError::from)?; Ok(()) } + pub fn resolve(&self, root: &str, stype: CMExportTypeWrapper) -> Option { + self.0.resolve(root, stype.into()) + } } #[pyfunction] diff --git a/cmtool-python/src/lib.rs b/cmtool-python/src/lib.rs index cfdd6d3d..a732aa27 100644 --- a/cmtool-python/src/lib.rs +++ b/cmtool-python/src/lib.rs @@ -37,7 +37,7 @@ mod pycmtool { #[pymodule_export] use super::{ DiscontinuousTransitionerWrapper, IterationStateWrapper, get_transitioner, - read_flowmap, read_rawflow, read_rawscalar, + read_flowmap, read_rawflow, read_rawscalar, scalar_from_data, }; } diff --git a/cmtool-python/src/rd.rs b/cmtool-python/src/rd.rs index 1137f11a..9213d125 100644 --- a/cmtool-python/src/rd.rs +++ b/cmtool-python/src/rd.rs @@ -1,10 +1,13 @@ use cmtool_data::RawData; +use cmtool_data::RawDataScalar; use numpy::PyArray1; use numpy::PyArray2; -use numpy::ndarray::{self}; +use numpy::PyUntypedArrayMethods; +use pyo3::exceptions::PyValueError; use pyo3::prelude::*; +/* Scalar */ -#[pyclass(name = "RawDataScalar")] +#[pyclass(name = "RawDataScalar", frozen)] pub struct RawDataScalarWrapper(cmtool_data::RawDataScalar); #[pymethods] impl RawDataScalarWrapper { @@ -16,13 +19,54 @@ impl RawDataScalarWrapper { #[getter] pub fn data(&self, py: Python<'_>) -> Py> { let values: Vec = self.0.values.iter().map(|f| f.value).collect(); + //from does not perform copy + let array = numpy::ndarray::Array1::from(values); + PyArray1::from_owned_array(py, array).unbind() + } +} - let array = ndarray::Array1::from(values); +#[pyfunction] +pub fn read_rawscalar(path: &str) -> PyResult { + if let Some(sc) = cmtool_data::RawDataScalar::read_raw(path) { + Ok(RawDataScalarWrapper(sc)) + } else { + Err(PyValueError::new_err("Scalar not found")) + } +} - PyArray1::from_owned_array(py, array).unbind() +#[pyfunction] +pub fn scalar_from_data<'py>( + _py: Python<'py>, + x: numpy::PyReadonlyArrayDyn<'py, f64>, +) -> PyResult { + if x.shape().len() != 1 { + return Err(PyValueError::new_err("Input array must be 1D.")); + } + + if !x.is_contiguous() { + return Err(PyValueError::new_err( + "Input array must be contiguous in memory.", + )); + } + + match x.as_slice() { + Ok(slice) => { + if slice.len() != 1 { + return Err(PyValueError::new_err( + "Input array must contain exactly one element.", + )); + } + let scalar = RawDataScalar::from(slice); + Ok(RawDataScalarWrapper(scalar)) + } + Err(_) => Err(PyValueError::new_err( + "Failed to convert the array to a contiguous slice.", + )), } } +/* Flows */ + #[pyclass(name = "RawDataFlux")] pub struct RawDataFluxWrapper(cmtool_data::RawDataFlux); #[pymethods] @@ -38,11 +82,6 @@ pub fn read_rawflow(path: &str) -> RawDataFluxWrapper { RawDataFluxWrapper(cmtool_data::RawDataFlux::read_raw(path).unwrap()) } -#[pyfunction] -pub fn read_rawscalar(path: &str) -> RawDataScalarWrapper { - RawDataScalarWrapper(cmtool_data::RawDataScalar::read_raw(path).unwrap()) -} - #[pyclass(name = "FlowMapDescriptor")] pub struct FlowMapDescriptorWrapper(cmtool_data::FlowMapDescriptor); #[pymethods] @@ -73,7 +112,7 @@ impl FlowMapDescriptorWrapper { #[getter] pub fn volumes(&self, py: Python<'_>) -> Py> { - let array = ndarray::Array1::from(self.0.volumes.clone()); + let array = numpy::ndarray::Array1::from(self.0.volumes.clone()); PyArray1::from_owned_array(py, array).unbind() } } diff --git a/cmtool-python/src/transitionner.rs b/cmtool-python/src/transitionner.rs index b5de2b55..63ebcd4e 100644 --- a/cmtool-python/src/transitionner.rs +++ b/cmtool-python/src/transitionner.rs @@ -8,24 +8,6 @@ use pyo3::prelude::*; #[pyclass(name = "DiscontinuousTransitioner")] pub struct DiscontinuousTransitionerWrapper(DiscontinuousTransitioner); -#[pyclass(name = "IterationState")] -pub struct IterationStateWrapper(Arc); - -#[pymethods] -impl IterationStateWrapper { - #[getter] - pub fn flowmap(&self, py: Python<'_>) -> (&[usize], &[usize], &[f64]) { - let t = &self.0.liquid.transition; - (t.row_indices(), t.col_indices(), t.values()) - } - - #[getter] - pub fn volumes(&self, py: Python<'_>) -> Py> { - let array = ndarray::Array1::from_vec(self.0.liquid.volumes.clone()); - PyArray1::from_owned_array(py, array).unbind() - } -} - #[pymethods] impl DiscontinuousTransitionerWrapper { fn advance(&mut self, current_time: f64, time_step: f64) -> IterationStateWrapper { @@ -54,6 +36,64 @@ impl DiscontinuousTransitionerWrapper { } } +#[pyclass(name = "IterationState", frozen)] +pub struct IterationStateWrapper(Arc); + +#[pyclass(name = "HydroState", frozen)] +pub struct HydroStateWrapper(*const cmtool_data::HydroState); + +unsafe impl Sync for HydroStateWrapper {} +unsafe impl Send for HydroStateWrapper {} + +#[pymethods] +impl HydroStateWrapper { + #[getter] + pub fn transition(&self, py: Python<'_>) -> (&[usize], &[usize], &[f64]) { + let t = unsafe { (*self.0).get_transition() }; + (t.row_indices(), t.col_indices(), t.values()) + } + #[getter] + pub fn volumes(&self, py: Python<'_>) -> Py> { + let deref = unsafe { &*self.0 }; + let array = deref.get_volume(); + let r = PyArray1::from_slice(py, array); + r.unbind() + } +} + +#[pymethods] +impl IterationStateWrapper { + #[getter] + pub fn liquid(&self) -> HydroStateWrapper { + let l = &self.0.liquid; + HydroStateWrapper(l) + } + + #[getter] + fn n_compartments(&self) -> usize { + self.0.n_compartments() + } + + #[getter] + fn get_gas(&self) -> Option { + self.0.gas.as_ref().map(|t| HydroStateWrapper(t)) + } + + fn misc(&self, key: &str, py: Python<'_>) -> Option>> { + if let Some(opt_m) = self.0.get(key) { + let r = PyArray1::from_slice(py, opt_m); + let a = r.unbind(); + Some(a) + } else { + None + } + } + + fn has_gas(&self) -> bool { + self.0.gas.is_some() + } +} + #[pyfunction] pub fn get_transitioner(root: &str) -> DiscontinuousTransitionerWrapper { let t: DiscontinuousTransitioner = cmtool_data::get_transitioner(root).unwrap(); From 2570a22a631d298cd5b0e85b1113879482951d04 Mon Sep 17 00:00:00 2001 From: bcasale Date: Mon, 8 Dec 2025 16:12:55 +0100 Subject: [PATCH 13/44] feat(example): full two phase flow reactive python example --- example/python/complete_reactive.py | 205 ++++++++++++++++++++++++++++ example/python/mixing_simple.py | 14 +- 2 files changed, 212 insertions(+), 7 deletions(-) create mode 100644 example/python/complete_reactive.py diff --git a/example/python/complete_reactive.py b/example/python/complete_reactive.py new file mode 100644 index 00000000..a8eb6f5a --- /dev/null +++ b/example/python/complete_reactive.py @@ -0,0 +1,205 @@ +""" +Simple Example Script: Reactive Simulation with pycmtool +====================================================== + +**Purpose:** +This script demonstrates how to use the `pycmtool` library to perform a simple mixing simulation. +It integrates a mass balance over time using a sparse matrix representation of the system +The core functionality is used for chemical or compartmental mixing simulations. + +**Key Features:** +- Uses `pycmtool` to construct flow sparse matrices from system states. +- Integrates mass balance equations using `scipy.integrate.solve_ivp`. +- Visualizes results with `matplotlib`. + +**Example Workflow:** +1. Define initial mass distribution. +2. Use `pycmtool` to advance the system state and construct sparse matrices. +3. Integrate the system over a specified duration. +4. Plot the results. + +**Author:** CASALE Benjamin +**Date:** 2025-12-08 +**Version:** 1.0 +""" + +import os + +import matplotlib.pyplot as plt +import numpy as np +import pycmtool +from scipy.integrate import solve_ivp +from typing import Callable + + +def integration( + two_phase_flow: bool, + fmt: "pycmtool.Transitioner", + mass_0: np.ndarray, + duration: float, + n_species, + f_reaction: Callable, +): + """ + Perform a mixing simulation by integrating the mass balance ODE over a specified duration. + + This function uses `pycmtool` to advance the system state and construct sparse matrices, + then integrates the mass balance equations using `scipy.integrate.solve_ivp`. + + Args: + fmt: A `pycmtool.Transitioner` object to advance the system state. + mass_0: Initial mass distribution (1D array of shape `(n_species * n_compartments,)`). + duration: Total simulation time (in seconds or any consistent time unit). + n_species: Number of species in the simulation . + """ + n_p = 2 if two_phase_flow else 1 + + def phase_dt(_mass, iphase, hydro_state, n_v): + transition = pycmtool.get_sparse_transition_matrix(hydro_state) + vol = hydro_state.volumes + # C = _mass/vol + # print(C.shape) + # C = np.zeros((dims[0],dims[1],dims[-1])) + C = _mass[:, :, iphase, :] / vol[:, None] + mm = np.zeros_like(C) + for i in range(n_v): + mm[:, :, i] = C[:, :, i] @ transition + return C, vol, mm + + def wrap_tpf(t: float, x: np.ndarray) -> np.ndarray: + """Wrapper function for ODE integration.""" + d_t = 0 # Not used for this transitioner + it = fmt.advance(t, d_t) + n_c = it.n_compartments + _mass = x.reshape((n_species, n_c, n_p, -1)) + n_v = _mass.shape[-1] + + C_l, vl, lflows = phase_dt(_mass, 0, it.liquid, n_v) + Cg, vg, gflows = phase_dt(_mass, 1, it.gas, n_v) + + R_l = f_reaction(C_l) * vl[:, np.newaxis] + + T = np.zeros_like(R_l) + T[2, :, :] = 0.2 * (0.03 * Cg[2, :, :] - C_l[2, :, :]) * vl[:, np.newaxis] + + dmdt = np.zeros_like(_mass) + dmdt[:, :, 0, :] = lflows + R_l + T + dmdt[:, :, 1, :] = gflows - T + return dmdt.reshape(-1, n_v) + + def wrap_l(t: float, x: np.ndarray) -> np.ndarray: + """Wrapper function for ODE integration.""" + d_t = 0 # Not used for this transitioner + it = fmt.advance(t, d_t) + n_c = it.n_compartments + _mass = x.reshape((n_species, n_c, n_p, -1)) + n_v = _mass.shape[-1] + C_l, vl, lflows = phase_dt(_mass, 0, it.liquid, n_v) + R_l = f_reaction(C_l) * vl[:, np.newaxis] + return (lflows + R_l).reshape(-1, n_v) + + wrap = wrap_tpf if two_phase_flow else wrap_l + return solve_ivp( + wrap, + (0, duration), + mass_0.reshape(-1), + method="BDF", + vectorized=True, + ) + + +def initial_c_distribution(n_c, n_p): + """ + Generate a random initial concentration distribution + for a simulation with `n_c` compartments. + """ + n = np.random.random((n_c, n_p)) + return n + + +def gm0(two_phase_flow: bool, C, it): + if two_phase_flow: + m = np.zeros_like(C) + m[:, :, 0] = C[:, :, 0] * it.liquid.volumes + m[:, :, 1] = C[:, :, 1] * it.gas.volumes + return m + else: + return C[:, :, 0] * it.liquid.volumes + + +def check_mixing(fmt, n_s, final_time: float, reaction_rate): + it = fmt.get_current() + n_c = it.n_compartments + + two_phase_flow = it.has_gas() + n_p = 2 if two_phase_flow else 1 + C = np.zeros((n_s, n_c, n_p)) + C[0, :, :] = 0.7 * initial_c_distribution(n_c, n_p) + C[1, :, :] = 0.2 * initial_c_distribution(n_c, n_p) + C[2, :, 1] = 300e-3 + m0 = gm0(two_phase_flow, C, it) + + sol = integration(two_phase_flow, fmt, m0, final_time, n_s, reaction_rate) + y = sol.y.reshape((n_s, n_c, n_p, -1)) + it = fmt.get_current() + plt.figure() + + plt.plot(y[0, :, 0, 0] / it.liquid.volumes, label="glucose") + plt.plot(y[1, :, 0, 0] / it.liquid.volumes, "--") + plt.title("concentration in all compartments init") + plt.legend() + + it = fmt.get_at(fmt.n_flowmaps - 1) + + plt.figure() + plt.plot(y[0, :, 0, -1] / it.liquid.volumes, label="glucose") + plt.plot(y[1, :, 0, -1] / it.liquid.volumes, "--") + plt.title("concentration in all compartments final") + plt.legend() + + plt.figure() + plt.plot(sol.t, y[0, n_c // 3, 0, :] / it.liquid.volumes[n_c // 3], label="glucose") + plt.plot(sol.t, y[1, n_c // 3, 0, :] / it.liquid.volumes[n_c // 3], "--") + plt.title("concentration = f(t)") + plt.legend() + + plt.figure() + plt.plot(y[2, n_c // 3, 0, :] / it.liquid.volumes[n_c // 3]) + # plt.plot(y[2, n_c//3,1,:]/it.gas.volumes[n_c//3],'--') + plt.legend() + + # plt.figure() + # plt.plot(y[2, :, 0, -1] / it.liquid.volumes) + # plt.plot(y[2, :, 1, -1] / it.liquid.volumes, "--") + # plt.title("concentration in all compartments final2") + # plt.legend() + + plt.show() + + +if __name__ == "__main__": + final_time = 15 * 3600 + root = "/home_pers/casale/Documents/thesis/cfd/sanofi" # os.environ["EXAMPLE_ROOT"] + + # root="/home-local/casale/Documents/code/rbiomc/examples/cma_data/0d_gas/" + N_SPECIES = 3 + + # Let CMTool read and load the full case automatically, ready to iterate + def _reaction_rate(Cl): + mum = 0.8 / 3600 + K_S = 0.1 + r = np.zeros_like(Cl) + mu = mum * Cl[0, :, :] / (Cl[0, :, :] + K_S)*Cl[2, :, :] / (Cl[2, :, :] + 1e-6) + dx = Cl[1, :, :] * mu + r[0, :, :] = -dx + r[1, :, :] = dx / 2 + r[2, :, :] = -dx / 20 + # for i in range(Cl.shape[-1]): + # mu = mum * Cl[0, :,i] / (Cl[0, :,i] + K_S) + # dx = Cl[1, :,i] * mu + # r[0,:,i]=-dx + # r[1,:,i]=dx/2 + return r + + fmt = pycmtool.data.get_transitioner(root) + check_mixing(fmt, N_SPECIES, final_time, _reaction_rate) diff --git a/example/python/mixing_simple.py b/example/python/mixing_simple.py index cee541c6..17258f55 100644 --- a/example/python/mixing_simple.py +++ b/example/python/mixing_simple.py @@ -54,10 +54,11 @@ def wrap(t: float, x: np.ndarray) -> np.ndarray: d_t = 0 # Not used for this transitioner # Advance iterator to the corresponding flowmap (according to t or dt) it = fmt.advance(t, d_t) + n_c = it.n_compartments + liquid_state = it.liquid # Get the transition matrix - transition = pycmtool.get_sparse_transition_matrix(it) - n_c = transition.shape[0] # Number of compartments - vol = it.volumes # Volume of each compartment + transition = pycmtool.get_sparse_transition_matrix(liquid_state) + vol = liquid_state.volumes # Volume of each compartment _mass = x.reshape((n_species, n_c)) # Reshape to (N_SPECIES, n_compartments) C = _mass / vol # Concentration: mass / volume return (C @ transition).reshape(-1) # Return flattened array for ODE solver @@ -88,19 +89,18 @@ def get_normalized(it, y, i): The normalization is performed by dividing each compartment's concentration by the mean concentration of the species across all compartments. This is useful for comparing relative concentrations. """ - vol = it.volumes + vol = it.liquid.volumes m0_c = y[0, :, i] return (m0_c / vol) / np.mean(m0_c / vol, axis=0) def check_mixing(fmt, final_time: float): it = fmt.get_current() - transition = pycmtool.get_sparse_transition_matrix(it) - n_c = transition.shape[0] + n_c = it.n_compartments C = np.zeros((N_SPECIES, n_c)) C[0, :] = initial_c_distribution(n_c) - vol = it.volumes + vol = it.liquid.volumes m0 = C * vol sol = integration(fmt, m0, final_time, N_SPECIES) From 1c83540bd44d08321ce07c9677c0c572bb9f2bb2 Mon Sep 17 00:00:00 2001 From: bcasale Date: Wed, 17 Dec 2025 16:03:30 +0100 Subject: [PATCH 14/44] feat(example): improve reactive simulation --- cmtool-python/pycmtool/__init__.py | 2 +- example/python/complete_reactive.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmtool-python/pycmtool/__init__.py b/cmtool-python/pycmtool/__init__.py index 89fab8a4..68a7509b 100644 --- a/cmtool-python/pycmtool/__init__.py +++ b/cmtool-python/pycmtool/__init__.py @@ -41,7 +41,7 @@ def get_sparse_transition_matrix(it): - return sparse_array_from_triplet(*it.flowmap) + return sparse_array_from_triplet(*it.transition) def sparse_array_from_triplet( diff --git a/example/python/complete_reactive.py b/example/python/complete_reactive.py index a8eb6f5a..cda69f32 100644 --- a/example/python/complete_reactive.py +++ b/example/python/complete_reactive.py @@ -179,17 +179,16 @@ def check_mixing(fmt, n_s, final_time: float, reaction_rate): if __name__ == "__main__": final_time = 15 * 3600 - root = "/home_pers/casale/Documents/thesis/cfd/sanofi" # os.environ["EXAMPLE_ROOT"] + root = os.environ["EXAMPLE_ROOT"] - # root="/home-local/casale/Documents/code/rbiomc/examples/cma_data/0d_gas/" N_SPECIES = 3 - - # Let CMTool read and load the full case automatically, ready to iterate def _reaction_rate(Cl): mum = 0.8 / 3600 K_S = 0.1 r = np.zeros_like(Cl) - mu = mum * Cl[0, :, :] / (Cl[0, :, :] + K_S)*Cl[2, :, :] / (Cl[2, :, :] + 1e-6) + mu = ( + mum * Cl[0, :, :] / (Cl[0, :, :] + K_S) * Cl[2, :, :] / (Cl[2, :, :] + 1e-6) + ) dx = Cl[1, :, :] * mu r[0, :, :] = -dx r[1, :, :] = dx / 2 @@ -201,5 +200,6 @@ def _reaction_rate(Cl): # r[1,:,i]=dx/2 return r + # Let CMTool read and load the full case automatically, ready to iterate fmt = pycmtool.data.get_transitioner(root) check_mixing(fmt, N_SPECIES, final_time, _reaction_rate) From c471f96ebde652d73eae4d61bec0d93dd079f3f4 Mon Sep 17 00:00:00 2001 From: bcasale Date: Mon, 19 Jan 2026 11:19:09 +0100 Subject: [PATCH 15/44] fix(parser): need by rbiomc, add run_id to domain_data to be compared if needed after parser --- cmtool-assemble/src/data.rs | 7 ++--- cmtool-assemble/src/lib.rs | 7 ++--- cmtool-assemble/src/map_generation.rs | 6 ++--- cmtool-assemble/src/parser/mod.rs | 18 ++++++------- cmtool-assemble/src/parser/reactors.rs | 37 ++++++++++++-------------- cmtool-data/src/case.rs | 5 ++-- 6 files changed, 36 insertions(+), 44 deletions(-) diff --git a/cmtool-assemble/src/data.rs b/cmtool-assemble/src/data.rs index 27698715..01bdc6ca 100644 --- a/cmtool-assemble/src/data.rs +++ b/cmtool-assemble/src/data.rs @@ -26,8 +26,8 @@ pub struct DomainInfo { pub compartment_cumsum: HashMap, pub total_number_compartment: usize, pub pfr_names: Vec, - pub cm_case_only:Option, - pub is_two_phase_flow:bool + pub cm_case_only: Option, + pub is_two_phase_flow: bool, } impl DomainInfo { pub fn get_relative_compartment_number( @@ -46,5 +46,6 @@ pub struct DomainData { pub connections: Option<[cmtool_data::RawDataFlux; 2]>, pub info: DomainInfo, pub feeds: Option, - pub case_path :String + pub case_path: String, + pub run_id: String, } diff --git a/cmtool-assemble/src/lib.rs b/cmtool-assemble/src/lib.rs index d10de6c7..9bf829fb 100644 --- a/cmtool-assemble/src/lib.rs +++ b/cmtool-assemble/src/lib.rs @@ -68,17 +68,14 @@ impl Parser { } else { std::fs::create_dir_all(root_dir)?; generate_flowmap(root_dir, &domain, &root.reactors, &mb)? - }; + }; domain.case_path = path; Ok(domain) } } -pub fn generate_domain( - root_dir: &str, - reactor_content: &str, -) -> Result { +pub fn generate_domain(root_dir: &str, reactor_content: &str) -> Result { let (_id, root) = Parser::start_parsing(reactor_content)?; let domain = Parser::continue_parsing(root, root_dir)?; diff --git a/cmtool-assemble/src/map_generation.rs b/cmtool-assemble/src/map_generation.rs index eaca1595..1963afbc 100644 --- a/cmtool-assemble/src/map_generation.rs +++ b/cmtool-assemble/src/map_generation.rs @@ -114,7 +114,7 @@ fn generate_partial_flowmap( generated_domain::ReactorsTypeContent::Reactor3D(reactor3_dtype) => { todo!("{:?}", reactor3_dtype) } - generated_domain::ReactorsTypeContent::ReactorFromFile(_)=>{ + generated_domain::ReactorsTypeContent::ReactorFromFile(_) => { todo!("") } } @@ -155,11 +155,9 @@ pub fn generate_flowmap( let path = format!("{}/cma_case", root); CMCaseJson::write_case(case, std::path::Path::new(&path))?; Ok(root.to_owned()) - }else{ + } else { Err(CMError::Custom("TODO ".to_owned())) } - - } // fn parse_generate() diff --git a/cmtool-assemble/src/parser/mod.rs b/cmtool-assemble/src/parser/mod.rs index d4990dac..f7f746a3 100644 --- a/cmtool-assemble/src/parser/mod.rs +++ b/cmtool-assemble/src/parser/mod.rs @@ -14,15 +14,14 @@ pub(super) use pfr_mb::PfrGlobalMassBalance; pub fn parse_domain( root: &generated_domain::RootElementType, ) -> Result<(DomainData, PfrGlobalMassBalance), CMError> { - - if root.reactors.content.is_empty() - { - return Err(CMError::Parse(serde_xml_rs::Error::Custom("At least one reactor required".to_owned()))); - } + if root.reactors.content.is_empty() { + return Err(CMError::Parse(serde_xml_rs::Error::Custom( + "At least one reactor required".to_owned(), + ))); + } let info = parse_reactor(&root.reactors)?; - let mut mass_balance = PfrGlobalMassBalance::new(&info.pfr_names); let raw_connections = root @@ -32,16 +31,17 @@ pub fn parse_domain( let mut pfeeds = None; if let Some(feeds) = &root.feeds { - pfeeds = parse_feed(&info, feeds, &mut mass_balance); + pfeeds = parse_feed(&info, feeds, &mut mass_balance); } mass_balance.validate()?; - + let run_id = root.run_id.clone(); Ok(( DomainData { connections: raw_connections, info, feeds: pfeeds, - case_path:String::new() + case_path: String::new(), + run_id, }, mass_balance, )) diff --git a/cmtool-assemble/src/parser/reactors.rs b/cmtool-assemble/src/parser/reactors.rs index 3b0acb7b..26b9ec41 100644 --- a/cmtool-assemble/src/parser/reactors.rs +++ b/cmtool-assemble/src/parser/reactors.rs @@ -157,10 +157,9 @@ pub fn parse_feed( .iter() .partition(|f| f.phase == *"liquid") .clone(); - if liquid_feeds.is_empty() && gas_feeds.is_empty() - { + if liquid_feeds.is_empty() && gas_feeds.is_empty() { return None; - } + } Some(ParsedFeeds { liq: parse_feed_phase(info, &liquid_feeds, PhaseCM::Liquid, mass_balance), gas: parse_feed_phase(info, &gas_feeds, PhaseCM::Gas, mass_balance), @@ -171,7 +170,7 @@ pub fn parse_reactor(reactors: &generated_domain::ReactorsType) -> Result { @@ -180,7 +179,7 @@ pub fn parse_reactor(reactors: &generated_domain::ReactorsType) -> Result { domain_info @@ -188,32 +187,30 @@ pub fn parse_reactor(reactors: &generated_domain::ReactorsType) -> Result{ - - let path = format!("{}/cma_case",reactor_from_file.path); - + generated_domain::ReactorsTypeContent::ReactorFromFile(reactor_from_file) => { + let path = format!("{}/cma_case", reactor_from_file.path); let path = PathBuf::from(path); let case = cmtool_data::read_case(path.as_path())?; // case.n_compartment() - domain_info.compartment_cumsum.insert(reactor_from_file.id.clone(),in_place_cumsum); + domain_info + .compartment_cumsum + .insert(reactor_from_file.id.clone(), in_place_cumsum); let n_c = case.n_compartment() as usize; domain_info.is_two_phase_flow = case.is_two_phase_flow(); - domain_info.total_number_compartment+=n_c; - in_place_cumsum+=n_c; - - if cm_case_only.is_none() - { + domain_info.total_number_compartment += n_c; + in_place_cumsum += n_c; + + if cm_case_only.is_none() { cm_case_only = Some(reactor_from_file.path.clone()); - } else{ + } else { todo!("Existing flowmap merge") - } - println!("{:?}",cm_case_only); - + } + println!("{:?}", cm_case_only); } generated_domain::ReactorsTypeContent::Reactor3D(reactor3_dtype) => { todo!("{:?}", reactor3_dtype) diff --git a/cmtool-data/src/case.rs b/cmtool-data/src/case.rs index e94d52a3..b76697d3 100644 --- a/cmtool-data/src/case.rs +++ b/cmtool-data/src/case.rs @@ -58,8 +58,7 @@ impl std::fmt::Display for CMCase { impl CMCase { pub fn n_compartment(&self) -> u32 { - if self.n_div.iter().find(|e| **e==0).is_some() - { + if self.n_div.iter().find(|e| **e == 0).is_some() { return 1; } self.n_div.iter().product() @@ -69,7 +68,7 @@ impl CMCase { self.is_reursive = !self.is_reursive; } - pub fn is_two_phase_flow(&self)->bool{ + pub fn is_two_phase_flow(&self) -> bool { self.paths.contains_key(&CMAExportType::GasVolume) } From 63a6337feaee647b12167321ac68ecf2a3bf116c Mon Sep 17 00:00:00 2001 From: bcasale Date: Tue, 20 Jan 2026 13:53:29 +0100 Subject: [PATCH 16/44] ci: remove macos-13 runner, change pr template --- .github/pull_request_template.md | 27 ++++++--------------------- .github/workflows/build.yml | 2 -- 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 79a4fea3..1ff7cbde 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -4,24 +4,9 @@ ## Content -- Scope: - - [ ] Code: if relevant, add affected target - - [ ] Build script: if relevant, add affected target - - [ ] Documentation - - [ ] CI - - [ ] Repository - - [ ] Other: ... -- Type of change: - - [ ] New feature(s) - - [ ] Fix: if relevant, add issue #... - - [ ] Testing - - [ ] Refactor - - [ ] Revert - - [ ] Chore -- Other: - - [ ] Breaking change - - [ ] New dependency -- Necessary follow-up: - - [ ] Needs documentation - - [ ] Needs testing - - [ ] Other: ... +### Main changes + + +## Issues + + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5b09aad2..51e3cdc3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -187,8 +187,6 @@ jobs: strategy: matrix: platform: - - runner: macos-13 - target: x86_64 - runner: macos-14 target: aarch64 steps: From b696f058d58e53a575f818b215a4ec2c083059dc Mon Sep 17 00:00:00 2001 From: bcasale Date: Wed, 11 Feb 2026 14:40:10 +0100 Subject: [PATCH 17/44] chore: fix license for cmtool-asssemble --- cmtool-assemble/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmtool-assemble/Cargo.toml b/cmtool-assemble/Cargo.toml index 959050a3..eb918a53 100644 --- a/cmtool-assemble/Cargo.toml +++ b/cmtool-assemble/Cargo.toml @@ -5,7 +5,7 @@ version.workspace = true authors.workspace = true description.workspace = true documentation.workspace = true - +license.workspace = true [lib] name = "cmtool_assemble" path = "src/lib.rs" From 822f80adad716cc08daf7b0bfd2718b575e7dbe7 Mon Sep 17 00:00:00 2001 From: Casale Date: Thu, 12 Feb 2026 11:21:07 +0100 Subject: [PATCH 18/44] chore: readme and cargo-deny --- Cargo.lock | 505 ++++++++--------------- README.md | 31 +- deny.toml | 165 ++++++++ documentations/docbook/src/cfd_to_cma.md | 5 + documentations/docbook/src/components.md | 4 +- documentations/docbook/src/main.md | 2 +- 6 files changed, 372 insertions(+), 340 deletions(-) create mode 100644 deny.toml diff --git a/Cargo.lock b/Cargo.lock index 7dd6f803..f68dcc4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,9 +29,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.20" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -44,9 +44,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" @@ -59,18 +59,18 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", @@ -79,9 +79,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" [[package]] name = "approx" @@ -104,6 +104,21 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "2.10.0" @@ -112,9 +127,9 @@ checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bytemuck" -version = "1.23.2" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" [[package]] name = "byteorder" @@ -122,17 +137,11 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" -[[package]] -name = "bytes" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" - [[package]] name = "cc" -version = "1.2.37" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", "shlex", @@ -140,15 +149,15 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.5.47" +version = "4.5.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" +checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" dependencies = [ "clap_builder", "clap_derive", @@ -156,9 +165,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.47" +version = "4.5.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" +checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" dependencies = [ "anstream", "anstyle", @@ -168,9 +177,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.47" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ "heck", "proc-macro2", @@ -180,9 +189,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] name = "cmtool" @@ -201,7 +210,7 @@ version = "0.1.4" dependencies = [ "cmtool-data", "ndarray", - "serde 1.0.225", + "serde 1.0.228", "serde-xml-rs", "serde_json", "serde_xml", @@ -238,7 +247,7 @@ dependencies = [ "nalgebra", "nalgebra-sparse", "ndarray", - "serde 1.0.225", + "serde 1.0.228", "serde_cbor", "serde_json", "thiserror", @@ -271,7 +280,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" dependencies = [ - "serde 1.0.225", + "serde 1.0.228", "termcolor", "unicode-width", ] @@ -293,9 +302,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.188" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47ac4eaf7ebe29e92f1b091ceefec7710a53a6f6154b2460afda626c113b65b9" +checksum = "747d8437319e3a2f43d93b341c137927ca70c0f5dabeea7a005a73665e247c7e" dependencies = [ "cc", "cxx-build", @@ -308,9 +317,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.188" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2abd4c3021eefbac5149f994c117b426852bca3a0aad227698527bca6d4ea657" +checksum = "b0f4697d190a142477b16aef7da8a99bfdc41e7e8b1687583c0d23a79c7afc1e" dependencies = [ "cc", "codespan-reporting", @@ -323,9 +332,9 @@ dependencies = [ [[package]] name = "cxxbridge-cmd" -version = "1.0.188" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f12fbc5888b2311f23e52a601e11ad7790d8f0dbb903ec26e2513bf5373ed70" +checksum = "d0956799fa8678d4c50eed028f2de1c0552ae183c76e976cf7ca8c4e36a7c328" dependencies = [ "clap", "codespan-reporting", @@ -337,15 +346,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.188" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d3dd7870af06e283f3f8ce0418019c96171c9ce122cfb9c8879de3d84388fd" +checksum = "23384a836ab4f0ad98ace7e3955ad2de39de42378ab487dc28d3990392cb283a" [[package]] name = "cxxbridge-macro" -version = "1.0.188" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26f0d82da663316786791c3d0e9f9edc7d1ee1f04bdad3d2643086a69d6256c" +checksum = "e6acc6b5822b9526adfb4fc377b67128fdd60aac757cc4a741a6278603f763cf" dependencies = [ "indexmap", "proc-macro2", @@ -393,15 +402,15 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "find-msvc-tools" -version = "0.1.1" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" -version = "1.1.2" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", @@ -422,95 +431,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - [[package]] name = "glam" version = "0.14.0" @@ -603,9 +523,9 @@ checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" [[package]] name = "glam" -version = "0.30.6" +version = "0.30.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d8e8d9db3feacb0bb4801b77e00c4d550f3e8d3e5303388fdfec3ff8a91f04d" +checksum = "19fc433e8437a212d1b6f1e68c7824af3aed907da60afa994e7f542d18d12aa9" [[package]] name = "half" @@ -615,9 +535,9 @@ checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heck" @@ -673,9 +593,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ "icu_collections", "icu_locale_core", @@ -687,9 +607,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" @@ -729,9 +649,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown", @@ -739,21 +659,24 @@ dependencies = [ [[package]] name = "indoc" -version = "2.0.6" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "lazy_static" @@ -763,9 +686,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.181" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" [[package]] name = "link-cplusplus" @@ -797,14 +720,14 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" dependencies = [ - "log 0.4.28", + "log 0.4.29", ] [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lz4_flex" @@ -838,9 +761,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memoffset" @@ -858,13 +781,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] name = "nalgebra" -version = "0.34.0" +version = "0.34.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cd59afb6639828b33677758314a4a1a745c15c02bc597095b851c8fd915cf49" +checksum = "c4d5b3eff5cd580f93da45e64715e8c20a3996342f1e466599cf7a267a0c2f5f" dependencies = [ "approx", "glam 0.14.0", @@ -882,7 +806,7 @@ dependencies = [ "glam 0.27.0", "glam 0.28.0", "glam 0.29.3", - "glam 0.30.6", + "glam 0.30.10", "matrixmultiply", "nalgebra-macros", "num-complex", @@ -1020,9 +944,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "parking_lot" @@ -1044,7 +968,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -1065,12 +989,6 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "pkg-config" version = "0.3.32" @@ -1079,15 +997,15 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" dependencies = [ "portable-atomic", ] @@ -1103,9 +1021,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -1180,7 +1098,7 @@ source = "git+https://github.com/elrnv/quick-xml.git?branch=binary-support4#d66f dependencies = [ "memchr", "ref-cast", - "serde 1.0.225", + "serde 1.0.228", ] [[package]] @@ -1191,15 +1109,13 @@ checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" dependencies = [ "encoding_rs", "memchr", - "serde 1.0.225", - "tokio", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -1221,18 +1137,18 @@ dependencies = [ [[package]] name = "ref-cast" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", @@ -1241,9 +1157,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -1253,9 +1169,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -1264,9 +1180,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "rustc-hash" @@ -1275,10 +1191,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] -name = "ryu" -version = "1.0.20" +name = "rustversion" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "safe_arch" @@ -1309,9 +1225,9 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" [[package]] name = "serde" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -1323,8 +1239,8 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc2215ce3e6a77550b80a1c37251b7d294febaf42e36e21b7b411e0bf54d540d" dependencies = [ - "log 0.4.28", - "serde 1.0.225", + "log 0.4.29", + "serde 1.0.228", "thiserror", "xml", ] @@ -1336,23 +1252,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" dependencies = [ "half", - "serde 1.0.225", + "serde 1.0.228", ] [[package]] name = "serde_core" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -1361,15 +1277,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", - "serde 1.0.225", + "serde 1.0.228", "serde_core", + "zmij", ] [[package]] @@ -1402,10 +1318,10 @@ dependencies = [ ] [[package]] -name = "slab" -version = "0.4.11" +name = "simd-adler32" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "smallvec" @@ -1427,9 +1343,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" dependencies = [ "proc-macro2", "quote", @@ -1449,9 +1365,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" +checksum = "b1dd07eb858a2067e2f3c7155d54e929265c264e6f37efe3ee7a8d1b5a1dd0ba" [[package]] name = "termcolor" @@ -1464,18 +1380,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -1492,21 +1408,11 @@ dependencies = [ "zerovec", ] -[[package]] -name = "tokio" -version = "1.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" -dependencies = [ - "bytes", - "pin-project-lite", -] - [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -1515,9 +1421,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -1526,9 +1432,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", ] @@ -1547,15 +1453,15 @@ checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" [[package]] name = "unicode-width" @@ -1571,14 +1477,14 @@ checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", - "serde 1.0.225", + "serde 1.0.228", ] [[package]] @@ -1596,19 +1502,19 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "vtkio" version = "0.7.0-rc1" -source = "git+https://github.com/elrnv/vtkio.git#e14c8ff3caf84ae5e21c2ee70aa18275d6e54051" +source = "git+https://github.com/elrnv/vtkio.git#942ce7e27f172cb3c5a04ec6179dd7f0417d1e82" dependencies = [ "base64", "bytemuck", "byteorder", "flate2", - "log 0.4.28", + "log 0.4.29", "lz4_flex", "nom", "num-derive", "num-traits", "quick-xml 0.36.0", - "serde 1.0.225", + "serde 1.0.228", "trim-in-place", "xz2", ] @@ -1632,12 +1538,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" @@ -1646,78 +1546,13 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.53.3" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.1.3", - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows-link", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - [[package]] name = "writeable" version = "0.6.2" @@ -1726,34 +1561,46 @@ checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "xml" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "838dd679b10a4180431ce7c2caa6e5585a7c8f63154c19ae99345126572e80cc" +checksum = "b8aa498d22c9bbaf482329839bc5620c46be275a19a812e9a22a2b07529a642a" [[package]] name = "xsd-parser" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e63d98ea458b38a90a586775ba5bfeb3408eb0b2816f3cc457a8dbad79609fca" +checksum = "7f871bee4eae8bccc80f30d5f5f6cbc14f14b80af05e2ee8ccf680a6296bd006" dependencies = [ "Inflector", "anyhow", "base64", + "bit-set", "bitflags", - "encoding_rs", - "futures", "indexmap", "parking_lot", "proc-macro2", "quick-xml 0.38.4", "quote", "regex", - "serde 1.0.225", "smallvec", "thiserror", "tracing", "unindent", "url", + "xsd-parser-types", +] + +[[package]] +name = "xsd-parser-types" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66abb5084a0e2687ec86fd399f4663adeed800f83cdb504070bd1a02027fb397" +dependencies = [ + "encoding_rs", + "indexmap", + "quick-xml 0.38.4", + "regex", + "thiserror", ] [[package]] @@ -1841,3 +1688,9 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/README.md b/README.md index 7a640994..ad02af97 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,38 @@ # RCMTool The specific goal of this repository is to develop a Compartment Modelling Tool. -A reactor compartment model is composed of two main parts: -- Flows between compartments (referred to as flowmaps) -- Scalar fields, with values within each compartment +Compartmental Modeling Approach (CMA) is a method that simplifies spatial dimensions by dividing the domain into smaller, uniform "compartments." Each compartment behaves as a homogeneous unit, connected to its neighboring compartments. The domain is represented by a scalar field, and the interactions between compartments are governed by a flowmap. -The compartmentalization process aims to reduce computational costs while maintaining a balance between computational efficiency and accuracy. +This approach significantly reduces computational complexity while maintaining an acceptable level of model accuracy. +## Overview -The core feature of the crate focuses on processing and simplifying Computational Fluid Dynamics (CFD) results obtained from simulations on fine computational grids into coarser grids, referred to as compartments. The provided tools enable convenient manipulation of compartment data and the configuration of simulations. -Additional features include the capability to generate Compartment Models directly from predefined reactor models (e.g., Plug Flow RReactor Model) +This crates aims to provides different feature to perform complete simulation using CMA. + +- Unified data type that can easily be written and read +- A Modeler to generate predefined simple model +- A CFD-to-CMA generator to use CFD results exported in Ensight-Gold Format +- Assemble flowmaps seamlessly + +## Crate organization + - cmtool: main cli + - cmtool-data: utilities for high and low level manipulation on flowmaps,compartment data + - cmtool-core: algorithm to transform results from fine-mesh transient CFD simuation into Compartment Models. + - cmtool-assemble: Generate flowmaps from from models and assemble them + - cmtool-python: Python bindings for cmtool-core,FlowmapTransitioner and case reading + - cmtool-cxx: Bindings for C++ use, expose FlowMapTranstioner API ## Objectives -This crate is a port of an existing C++ tool. The objective of the Rust implementation is to evaluate the efficiency of the Rust language for performing this type of operation, with a particular focus on maintaining code readability, maintainability, and performance. +This crate is a port of an existing C++ tool. The objective of the Rust implementation is to evaluate the efficiency of the Rust language to perform this type of operation, with a particular focus on maintaining code readability, maintainability, and performance. +## Getting started -### References -- Ensight gold specifications : - - https://dav.lbl.gov/archive/NERSC/Software/ensight/doc/Manuals/UserManual.pdf ## Authors - **CASALE Benjamin** ## LICENCE -This work is under GNU Lesser General Public License v3.0 or later (LGPL-3.0-or-later) \ No newline at end of file +This work is under GNU Lesser General Public License v3.0 or later (LGPL-3.0-or-later) diff --git a/deny.toml b/deny.toml new file mode 100644 index 00000000..bc318c19 --- /dev/null +++ b/deny.toml @@ -0,0 +1,165 @@ +[graph] +targets = [ +] +all-features = true +no-default-features = false + +[output] +feature-depth = 1 + +[advisories] +ignore = [ +] + +[licenses] +allow = [ + "GPL-3.0-or-later", + "Zlib", + "BSD-2-Clause", + "BSD-3-Clause", + "MIT", + "Unicode-3.0", + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", +] + +confidence-threshold = 0.8 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + #{ allow = ["Zlib"], crate = "adler32" }, +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +#[[licenses.clarify]] +# The package spec the clarification applies to +#crate = "ring" +# The SPDX expression for the license requirements of the crate +#expression = "MIT AND ISC AND OpenSSL" +# One or more files in the crate's source used as the "source of truth" for +# the license expression. If the contents match, the clarification will be used +# when running the license check, otherwise the clarification will be ignored +# and the crate will be checked normally, which may produce warnings or errors +# depending on the rest of your configuration +#license-files = [ +# Each entry is a crate relative path, and the (opaque) hash of its contents +#{ path = "LICENSE", hash = 0xbd0eed23 } +#] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries. +# To see how to mark a crate as unpublished (to the official registry), +# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. +ignore = false +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "warn" +# Lint level for when a crate version requirement is `*` +wildcards = "allow" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# The default lint level for `default` features for crates that are members of +# the workspace that is being checked. This can be overridden by allowing/denying +# `default` on a crate-by-crate basis if desired. +workspace-default-features = "allow" +# The default lint level for `default` features for external crates that are not +# members of the workspace. This can be overridden by allowing/denying `default` +# on a crate-by-crate basis if desired. +external-default-features = "allow" +# List of crates that are allowed. Use with care! +allow = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" }, +] +# If true, workspace members are automatically allowed even when using deny-by-default +# This is useful for organizations that want to deny all external dependencies by default +# but allow their own workspace crates without having to explicitly list them +allow-workspace = false +# List of crates to deny +deny = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" }, + # Wrapper crates can optionally be specified to allow the crate when it + # is a direct dependency of the otherwise banned crate + #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] }, +] + +# List of features to allow/deny +# Each entry the name of a crate and a version range. If version is +# not specified, all versions will be matched. +#[[bans.features]] +#crate = "reqwest" +# Features to not allow +#deny = ["json"] +# Features to allow +#allow = [ +# "rustls", +# "__rustls", +# "__tls", +# "hyper-rustls", +# "rustls", +# "rustls-pemfile", +# "rustls-tls-webpki-roots", +# "tokio-rustls", +# "webpki-roots", +#] +# If true, the allowed features must exactly match the enabled feature set. If +# this is set there is no point setting `deny` +#exact = true + +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite. +skip-tree = [ + #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies + #{ crate = "ansi_term@0.11.0", depth = 20 }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "warn" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "warn" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] + +[sources.allow-org] +# github.com organizations to allow git sources for +github = [] +# gitlab.com organizations to allow git sources for +gitlab = [] +# bitbucket.org organizations to allow git sources for +bitbucket = [] diff --git a/documentations/docbook/src/cfd_to_cma.md b/documentations/docbook/src/cfd_to_cma.md index 9fc989eb..776a70a9 100644 --- a/documentations/docbook/src/cfd_to_cma.md +++ b/documentations/docbook/src/cfd_to_cma.md @@ -1 +1,6 @@ # CFD-To-CMA + +## References + +- Ensight gold specifications : + - https://dav.lbl.gov/archive/NERSC/Software/ensight/doc/Manuals/UserManual.pdf \ No newline at end of file diff --git a/documentations/docbook/src/components.md b/documentations/docbook/src/components.md index 8c298583..35766bf4 100644 --- a/documentations/docbook/src/components.md +++ b/documentations/docbook/src/components.md @@ -5,7 +5,7 @@ This crates aims to provides different feature to perform complete simulation us - Unified data type that can easily be written and read - A Modeler to generate predefined simple model - A CFD-to-CMA generator to use CFD results exported in Ensight-Gold Format - +- Assemble flowmaps seamlessly # Crate organization @@ -31,4 +31,4 @@ Python bindings, namely binds cmtool-core generation from Ensight case, FlowmapT ## cmtool-cxx -Bindings for c++ use, expose FlowMapTranstioner API \ No newline at end of file +Bindings for c++ use, expose FlowMapTranstioner API diff --git a/documentations/docbook/src/main.md b/documentations/docbook/src/main.md index 6bf56ff6..9c13f568 100644 --- a/documentations/docbook/src/main.md +++ b/documentations/docbook/src/main.md @@ -3,7 +3,7 @@ The *CMTool* project aims to develop a set of tool to perform simulation using a compartment modelling approach. Compartment modeling is an approach used to reduce computational cost in simulation while maintaining a balance between efficiency and precision. -The core part of the crate is focused on process and simplify computational fluid dynamics (CFD) results obtained from simulations on fine computational grids into coarser grids, known as compartments.Additonal features includes generation of Compartment Model drectly from models (namely Plug Flow Reactor Model) +The core part of the crate is focused on process and simplify computational fluid dynamics (CFD) results obtained from simulations on fine computational grids into coarser grids, known as compartments. Additonal features includes generation of Compartment Model drectly from models (namely Plug Flow Reactor Model) ## Objectives From afd03860151f38d1641a3a3d0055af41244c09ba Mon Sep 17 00:00:00 2001 From: bcasale Date: Mon, 16 Feb 2026 08:43:40 +0100 Subject: [PATCH 19/44] chore: update project meson version to have uniform versioning --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 073afa0b..1ac9df78 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'rcmtool-cxx', 'cpp', - version: '0.1.0', + version: '0.1.4', meson_version: '>= 1.3.0', ) out_dir = meson.current_build_dir() + '/rust_target' From bca7df10f44b6bae47b398d85e79079a3828aa79 Mon Sep 17 00:00:00 2001 From: bcasale Date: Tue, 17 Feb 2026 10:52:28 +0100 Subject: [PATCH 20/44] chore: prek,clippy,update and clean dependencies, fix docstring tests --- .pre-commit-config.yaml | 20 +++- Cargo.lock | 106 +++++------------- Cargo.toml | 19 +++- cmtool-assemble/build.rs | 5 + cmtool-assemble/src/generators.rs | 2 +- cmtool-assemble/src/map_generation.rs | 2 +- .../src/parser/generated_domain.rs | 9 +- cmtool-assemble/src/parser/mod.rs | 2 +- cmtool-assemble/src/parser/reactors.rs | 1 - cmtool-core/src/grid/mod.rs | 2 +- cmtool-core/src/utils/area.rs | 4 +- cmtool-data/Cargo.toml | 2 +- cmtool-data/src/case.rs | 11 +- cmtool-data/src/descriptors.rs | 2 +- cmtool-data/src/lib.rs | 2 + cmtool-data/src/transitioner/mod.rs | 6 +- cmtool-python/Cargo.toml | 4 +- cmtool-python/src/transitionner.rs | 3 +- cmtool/src/main.rs | 1 - examples/src/map_generation/mod.rs | 2 +- 20 files changed, 94 insertions(+), 111 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8d14e23a..cfb19055 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,10 +1,18 @@ +fail_fast: false repos: -- repo: https://github.com/doublify/pre-commit-rust + - repo: https://github.com/doublify/pre-commit-rust rev: v1.0 + hooks: - # - id: cargo-check - # args: ["--workspace"] - - id: fmt + - id: cargo-check + args: ["--workspace"] + - id: fmt args: ["--", "--check"] - # - id: clippy - # args: ["--", "-D", "warnings"] \ No newline at end of file + - id: clippy + args: ["--", "-D", "warnings"] + + - repo: https://github.com/EmbarkStudios/cargo-deny + rev: 0.19.0 + hooks: + - id: cargo-deny + args: ["--all-features", "check", "ban", "licenses", "sources"] diff --git a/Cargo.lock b/Cargo.lock index f68dcc4c..bac47e5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -121,9 +121,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "bytemuck" @@ -139,9 +139,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.55" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "shlex", @@ -155,9 +155,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.5.58" +version = "4.5.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" +checksum = "c5caf74d17c3aec5495110c34cc3f78644bfa89af6c8993ed4de2790e49b6499" dependencies = [ "clap_builder", "clap_derive", @@ -165,9 +165,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.58" +version = "4.5.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" +checksum = "370daa45065b80218950227371916a1633217ae42b2715b2287b606dcd618e24" dependencies = [ "anstream", "anstyle", @@ -248,7 +248,6 @@ dependencies = [ "nalgebra-sparse", "ndarray", "serde 1.0.228", - "serde_cbor", "serde_json", "thiserror", ] @@ -527,12 +526,6 @@ version = "0.30.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19fc433e8437a212d1b6f1e68c7824af3aed907da60afa994e7f542d18d12aa9" -[[package]] -name = "half" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" - [[package]] name = "hashbrown" version = "0.16.1" @@ -657,15 +650,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "indoc" -version = "2.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" -dependencies = [ - "rustversion", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -686,9 +670,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.181" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "link-cplusplus" @@ -765,15 +749,6 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -839,9 +814,9 @@ dependencies = [ [[package]] name = "ndarray" -version = "0.16.1" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841" +checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d" dependencies = [ "matrixmultiply", "num-complex", @@ -922,9 +897,9 @@ dependencies = [ [[package]] name = "numpy" -version = "0.24.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7cfbf3f0feededcaa4d289fe3079b03659e85c5b5a177f4ba6fb01ab4fb3e39" +checksum = "778da78c64ddc928ebf5ad9df5edf0789410ff3bdbf3619aed51cd789a6af1e2" dependencies = [ "libc", "ndarray", @@ -1030,37 +1005,32 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.24.2" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5203598f366b11a02b13aa20cab591229ff0a89fd121a308a5df751d5fc9219" +checksum = "14c738662e2181be11cb82487628404254902bb3225d8e9e99c31f3ef82a405c" dependencies = [ - "cfg-if", - "indoc", "libc", - "memoffset", "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", - "unindent", ] [[package]] name = "pyo3-build-config" -version = "0.24.2" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99636d423fa2ca130fa5acde3059308006d46f98caac629418e53f7ebb1e9999" +checksum = "f9ca0864a7dd3c133a7f3f020cbff2e12e88420da854c35540fd20ce2d60e435" dependencies = [ - "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.24.2" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78f9cf92ba9c409279bc3305b5409d90db2d2c22392d443a87df3a1adad59e33" +checksum = "9dfc1956b709823164763a34cc42bbfd26b8730afa77809a3df8b94a3ae3b059" dependencies = [ "libc", "pyo3-build-config", @@ -1068,9 +1038,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.24.2" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b999cb1a6ce21f9a6b147dcf1be9ffedf02e0043aec74dc390f3007047cecd9" +checksum = "29dc660ad948bae134d579661d08033fbb1918f4529c3bbe3257a68f2009ddf2" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -1080,9 +1050,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.24.2" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "822ece1c7e1012745607d5cf0bcb2874769f0f7cb34c4cde03b9358eb9ef911a" +checksum = "e78cd6c6d718acfcedf26c3d21fe0f053624368b0d44298c55d7138fde9331f7" dependencies = [ "heck", "proc-macro2", @@ -1190,12 +1160,6 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - [[package]] name = "safe_arch" version = "0.7.4" @@ -1245,16 +1209,6 @@ dependencies = [ "xml", ] -[[package]] -name = "serde_cbor" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" -dependencies = [ - "half", - "serde 1.0.228", -] - [[package]] name = "serde_core" version = "1.0.228" @@ -1343,9 +1297,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.115" +version = "2.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" +checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" dependencies = [ "proc-macro2", "quote", @@ -1365,9 +1319,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.13.4" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1dd07eb858a2067e2f3c7155d54e929265c264e6f37efe3ee7a8d1b5a1dd0ba" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" [[package]] name = "termcolor" @@ -1459,9 +1413,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-width" diff --git a/Cargo.toml b/Cargo.toml index 005d477c..9c311a9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,13 @@ [workspace] -members = ["cmtool-data", "cmtool-core", "cmtool", "cmtool-python","cmtool-cxx" ,'cmtool-assemble',"examples"] +members = [ + "cmtool-data", + "cmtool-core", + "cmtool", + "cmtool-python", + "cmtool-cxx", + 'cmtool-assemble', + "examples", +] resolver = "3" metadata.crane.name = "cmtool" @@ -30,7 +38,7 @@ cmtool-data = { path = "./cmtool-data" } cmtool-assemble = { path = "./cmtool-assemble" } serde = { version = "1.0.219", features = ["derive"] } -serde_cbor = "0.11.2" +#serde_cbor = "0.11.2" serde_json = "1.0.140" serde-xml-rs = "0.8.1" serde_xml = "0.9.1" @@ -39,9 +47,10 @@ enum_dispatch = "0.3.13" clap = { version = "4.5.41", features = ["derive"] } xsd-parser = "1.3.0" # -nalgebra = "0.34.0" +nalgebra = "0.34.1" nalgebra-sparse = "0.11.0" -ndarray = "0.16.1" +ndarray = "0.17.2" # python -pyo3 = { version = "0.24.0", features = ["abi3-py310", "extension-module"] } +numpy = "0.28.0" +pyo3 = { version = "0.28.1", features = ["abi3-py310", "extension-module"] } diff --git a/cmtool-assemble/build.rs b/cmtool-assemble/build.rs index fb99a7ce..50435e11 100644 --- a/cmtool-assemble/build.rs +++ b/cmtool-assemble/build.rs @@ -49,6 +49,11 @@ fn domain_schema() -> Result<(), Error> { cfg.generator.flags.insert(GeneratorFlags::all()); let code = generate(cfg).expect("Failed to generate Rust code from XSD"); let mut file = File::create("src/parser/generated_domain.rs")?; + file.write_all( + b"#![allow(clippy::all)]\n + #![allow(dead_code)]\n + #![allow(unused_imports)]\n\n\n", + )?; file.write_all(code.to_string().as_bytes())?; Ok(()) } diff --git a/cmtool-assemble/src/generators.rs b/cmtool-assemble/src/generators.rs index a6e670cb..060a157a 100644 --- a/cmtool-assemble/src/generators.rs +++ b/cmtool-assemble/src/generators.rs @@ -134,7 +134,7 @@ impl Generator { self.generate_0d_phase(&mut case, gas_volume, PhaseCM::Gas, dest)?; } - if let Some(scalars) = fields { + if let Some(_scalars) = fields { todo!("Scalar field") } diff --git a/cmtool-assemble/src/map_generation.rs b/cmtool-assemble/src/map_generation.rs index 1963afbc..b47db88b 100644 --- a/cmtool-assemble/src/map_generation.rs +++ b/cmtool-assemble/src/map_generation.rs @@ -5,8 +5,8 @@ use crate::data::DomainData; use crate::generators::{Generator, PFRDescription}; use crate::parser::generated_domain; use crate::parser::{PfrGlobalMassBalance, generated_domain::Reactor0DType}; -use cmtool_data::{CCMCaseInfo, CMCaseWriter, DataError}; use cmtool_data::{CMCaseJson, CMCaseReader, PhaseCM}; +use cmtool_data::{CMCaseWriter, DataError}; fn get_volume(size: &generated_domain::GeneralSizeType) -> f64 { match &size { diff --git a/cmtool-assemble/src/parser/generated_domain.rs b/cmtool-assemble/src/parser/generated_domain.rs index 89743883..7ca11328 100644 --- a/cmtool-assemble/src/parser/generated_domain.rs +++ b/cmtool-assemble/src/parser/generated_domain.rs @@ -1 +1,8 @@ -use serde :: { Deserialize , Serialize } ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct BaseReactorType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct ConnectionsType { # [serde (default , rename = "Flux")] pub flux : Vec < FluxType > , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct DimensionType { # [serde (default , rename = "@unit")] pub unit : Option < :: std :: string :: String > , # [serde (rename = "#text")] pub content : :: core :: primitive :: f32 , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct FeedFluxType { # [serde (rename = "@phase")] pub phase : :: std :: string :: String , # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "Source")] pub source : NodeType , # [serde (rename = "Target")] pub target : NodeType , # [serde (rename = "Value")] pub value : DimensionType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct FeedsType { # [serde (default , rename = "Flux")] pub flux : Vec < FeedFluxType > , } pub type FlowType = DimensionType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct FluxType { # [serde (rename = "@phase")] pub phase : :: std :: string :: String , # [serde (rename = "Source")] pub source : NodeType , # [serde (rename = "Target")] pub target : NodeType , # [serde (rename = "Value")] pub value : DimensionType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub enum GeneralSizeType { # [serde (rename = "Volume")] Volume (DimensionType) , # [serde (rename = "Dimension")] Dimension (SizeCylindricalType) , } pub type LengthType = DimensionType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct NodeType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "@compartment_id")] pub compartment_id : :: core :: primitive :: usize , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct Reactor0DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct Reactor1DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , # [serde (rename = "Compartments")] pub compartments : :: core :: primitive :: usize , # [serde (rename = "Dispersion")] pub dispersion : DimensionType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct Reactor3DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , # [serde (rename = "file")] pub file : :: std :: string :: String , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct ReactorFromFileType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "Path")] pub path : :: std :: string :: String , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct ReactorsType { # [serde (rename = "#content")] pub content : Vec < ReactorsTypeContent > , } # [derive (Debug , Clone , Serialize , Deserialize)] pub enum ReactorsTypeContent { # [serde (rename = "Reactor0D")] Reactor0D (Reactor0DType) , # [serde (rename = "Reactor1D")] Reactor1D (Reactor1DType) , # [serde (rename = "Reactor3D")] Reactor3D (Reactor3DType) , # [serde (rename = "ReactorFromFile")] ReactorFromFile (ReactorFromFileType) , } pub type Root = RootElementType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct RootElementType { # [serde (rename = "@run_id")] pub run_id : :: std :: string :: String , # [serde (rename = "@version")] pub version : :: core :: primitive :: i32 , # [serde (rename = "Reactors")] pub reactors : ReactorsType , # [serde (default , rename = "Connections")] pub connections : Option < ConnectionsType > , # [serde (default , rename = "Feeds")] pub feeds : Option < FeedsType > , } # [derive (Debug , Clone , Serialize , Deserialize)] pub struct SizeCylindricalType { # [serde (rename = "Diameter")] pub diameter : DimensionType , # [serde (rename = "Length")] pub length : DimensionType , } # [derive (Debug , Clone , Serialize , Deserialize)] pub enum SizeSpecificationType { # [serde (rename = "Volume")] Volume (DimensionType) , # [serde (rename = "CylindricalDimensions")] CylindricalDimensions (SizeSpecificationCylindricalDimensionsElementType) , } pub type TimeType = DimensionType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct VolumeFractionType { # [serde (default , rename = "@phase")] pub phase : Option < :: std :: string :: String > , # [serde (rename = "#text")] pub content : :: core :: primitive :: f32 , } pub type VolumeType = DimensionType ; # [derive (Debug , Clone , Serialize , Deserialize)] pub struct SizeSpecificationCylindricalDimensionsElementType { # [serde (rename = "Diameter")] pub diameter : DimensionType , # [serde (rename = "Length")] pub length : DimensionType , } pub mod xs { use serde :: { Deserialize , Serialize } ; # [derive (Debug , Clone , Serialize , Deserialize , Default)] pub struct EntitiesType (pub Vec < :: std :: string :: String >) ; pub type EntityType = EntitiesType ; pub type IdType = :: std :: string :: String ; pub type IdrefType = :: std :: string :: String ; pub type IdrefsType = EntitiesType ; pub type NcNameType = :: std :: string :: String ; pub type NmtokenType = :: std :: string :: String ; pub type NmtokensType = EntitiesType ; pub type NotationType = :: std :: string :: String ; pub type NameType = :: std :: string :: String ; pub type QNameType = :: std :: string :: String ; pub type AnySimpleType = :: std :: string :: String ; pub type AnyUriType = :: std :: string :: String ; pub type Base64BinaryType = :: std :: string :: String ; pub type BooleanType = :: core :: primitive :: bool ; pub type ByteType = :: core :: primitive :: i8 ; pub type DateType = :: std :: string :: String ; pub type DateTimeType = :: std :: string :: String ; pub type DecimalType = :: core :: primitive :: f64 ; pub type DoubleType = :: core :: primitive :: f64 ; pub type DurationType = :: std :: string :: String ; pub type FloatType = :: core :: primitive :: f32 ; pub type GDayType = :: std :: string :: String ; pub type GMonthType = :: std :: string :: String ; pub type GMonthDayType = :: std :: string :: String ; pub type GYearType = :: std :: string :: String ; pub type GYearMonthType = :: std :: string :: String ; pub type HexBinaryType = :: std :: string :: String ; pub type IntType = :: core :: primitive :: i32 ; pub type IntegerType = :: core :: primitive :: i32 ; pub type LanguageType = :: std :: string :: String ; pub type LongType = :: core :: primitive :: i64 ; pub type NegativeIntegerType = :: core :: primitive :: isize ; pub type NonNegativeIntegerType = :: core :: primitive :: usize ; pub type NonPositiveIntegerType = :: core :: primitive :: isize ; pub type NormalizedStringType = :: std :: string :: String ; pub type PositiveIntegerType = :: core :: primitive :: usize ; pub type ShortType = :: core :: primitive :: i16 ; pub type StringType = :: std :: string :: String ; pub type TimeType = :: std :: string :: String ; pub type TokenType = :: std :: string :: String ; pub type UnsignedByteType = :: core :: primitive :: u8 ; pub type UnsignedIntType = :: core :: primitive :: u32 ; pub type UnsignedLongType = :: core :: primitive :: u64 ; pub type UnsignedShortType = :: core :: primitive :: u16 ; } \ No newline at end of file +#![allow(clippy::all)] + + #![allow(dead_code)] + + #![allow(unused_imports)] + + +use serde :: { Deserialize , Serialize } ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct BaseReactorType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct ConnectionsType { # [serde (default , rename = "Flux")] pub flux : :: std :: vec :: Vec < FluxType > , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct DimensionType { # [serde (default , rename = "@unit")] pub unit : :: core :: option :: Option < :: std :: string :: String > , # [serde (rename = "#text")] pub content : :: core :: primitive :: f32 , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct FeedFluxType { # [serde (rename = "@phase")] pub phase : :: std :: string :: String , # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "Source")] pub source : NodeType , # [serde (rename = "Target")] pub target : NodeType , # [serde (rename = "Value")] pub value : DimensionType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct FeedsType { # [serde (default , rename = "Flux")] pub flux : :: std :: vec :: Vec < FeedFluxType > , } pub type FlowType = DimensionType ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct FluxType { # [serde (rename = "@phase")] pub phase : :: std :: string :: String , # [serde (rename = "Source")] pub source : NodeType , # [serde (rename = "Target")] pub target : NodeType , # [serde (rename = "Value")] pub value : DimensionType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub enum GeneralSizeType { # [serde (rename = "Volume")] Volume (DimensionType) , # [serde (rename = "Dimension")] Dimension (SizeCylindricalType) , } pub type LengthType = DimensionType ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct NodeType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "@compartment_id")] pub compartment_id : :: core :: primitive :: usize , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct Reactor0DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct Reactor1DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , # [serde (rename = "Compartments")] pub compartments : :: core :: primitive :: usize , # [serde (rename = "Dispersion")] pub dispersion : DimensionType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct Reactor3DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , # [serde (rename = "file")] pub file : :: std :: string :: String , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct ReactorFromFileType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "Path")] pub path : :: std :: string :: String , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct ReactorsType { # [serde (rename = "#content")] pub content : :: std :: vec :: Vec < ReactorsTypeContent > , } # [derive (Clone , Debug , Deserialize , Serialize)] pub enum ReactorsTypeContent { # [serde (rename = "Reactor0D")] Reactor0D (Reactor0DType) , # [serde (rename = "Reactor1D")] Reactor1D (Reactor1DType) , # [serde (rename = "Reactor3D")] Reactor3D (Reactor3DType) , # [serde (rename = "ReactorFromFile")] ReactorFromFile (ReactorFromFileType) , } pub type Root = RootElementType ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct RootElementType { # [serde (rename = "@run_id")] pub run_id : :: std :: string :: String , # [serde (rename = "@version")] pub version : :: core :: primitive :: i32 , # [serde (rename = "Reactors")] pub reactors : ReactorsType , # [serde (default , rename = "Connections")] pub connections : :: core :: option :: Option < ConnectionsType > , # [serde (default , rename = "Feeds")] pub feeds : :: core :: option :: Option < FeedsType > , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct SizeCylindricalType { # [serde (rename = "Diameter")] pub diameter : DimensionType , # [serde (rename = "Length")] pub length : DimensionType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub enum SizeSpecificationType { # [serde (rename = "Volume")] Volume (DimensionType) , # [serde (rename = "CylindricalDimensions")] CylindricalDimensions (SizeSpecificationCylindricalDimensionsElementType) , } pub type TimeType = DimensionType ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct VolumeFractionType { # [serde (default , rename = "@phase")] pub phase : :: core :: option :: Option < :: std :: string :: String > , # [serde (rename = "#text")] pub content : :: core :: primitive :: f32 , } pub type VolumeType = DimensionType ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct SizeSpecificationCylindricalDimensionsElementType { # [serde (rename = "Diameter")] pub diameter : DimensionType , # [serde (rename = "Length")] pub length : DimensionType , } pub mod xs { use serde :: { Deserialize , Serialize } ; # [derive (Clone , Debug , Default , Deserialize , Serialize)] pub struct EntitiesType (pub :: std :: vec :: Vec < :: std :: string :: String >) ; pub type EntityType = EntitiesType ; pub type IdType = :: std :: string :: String ; pub type IdrefType = :: std :: string :: String ; pub type IdrefsType = EntitiesType ; pub type NcNameType = :: std :: string :: String ; pub type NmtokenType = :: std :: string :: String ; pub type NmtokensType = EntitiesType ; pub type NotationType = :: std :: string :: String ; pub type NameType = :: std :: string :: String ; pub type QNameType = :: std :: string :: String ; pub type AnySimpleType = :: std :: string :: String ; pub type AnyUriType = :: std :: string :: String ; pub type Base64BinaryType = :: std :: string :: String ; pub type BooleanType = :: core :: primitive :: bool ; pub type ByteType = :: core :: primitive :: i8 ; pub type DateType = :: std :: string :: String ; pub type DateTimeType = :: std :: string :: String ; pub type DecimalType = :: core :: primitive :: f64 ; pub type DoubleType = :: core :: primitive :: f64 ; pub type DurationType = :: std :: string :: String ; pub type FloatType = :: core :: primitive :: f32 ; pub type GDayType = :: std :: string :: String ; pub type GMonthType = :: std :: string :: String ; pub type GMonthDayType = :: std :: string :: String ; pub type GYearType = :: std :: string :: String ; pub type GYearMonthType = :: std :: string :: String ; pub type HexBinaryType = :: std :: string :: String ; pub type IntType = :: core :: primitive :: i32 ; pub type IntegerType = :: core :: primitive :: i32 ; pub type LanguageType = :: std :: string :: String ; pub type LongType = :: core :: primitive :: i64 ; pub type NegativeIntegerType = :: core :: primitive :: isize ; pub type NonNegativeIntegerType = :: core :: primitive :: usize ; pub type NonPositiveIntegerType = :: core :: primitive :: isize ; pub type NormalizedStringType = :: std :: string :: String ; pub type PositiveIntegerType = :: core :: primitive :: usize ; pub type ShortType = :: core :: primitive :: i16 ; pub type StringType = :: std :: string :: String ; pub type TimeType = :: std :: string :: String ; pub type TokenType = :: std :: string :: String ; pub type UnsignedByteType = :: core :: primitive :: u8 ; pub type UnsignedIntType = :: core :: primitive :: u32 ; pub type UnsignedLongType = :: core :: primitive :: u64 ; pub type UnsignedShortType = :: core :: primitive :: u16 ; } \ No newline at end of file diff --git a/cmtool-assemble/src/parser/mod.rs b/cmtool-assemble/src/parser/mod.rs index f7f746a3..0b8ff6d8 100644 --- a/cmtool-assemble/src/parser/mod.rs +++ b/cmtool-assemble/src/parser/mod.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later -#[allow(clippy::all)] + #[rustfmt::skip] pub mod generated_domain; use crate::{CMError, DomainData}; diff --git a/cmtool-assemble/src/parser/reactors.rs b/cmtool-assemble/src/parser/reactors.rs index 26b9ec41..1b323132 100644 --- a/cmtool-assemble/src/parser/reactors.rs +++ b/cmtool-assemble/src/parser/reactors.rs @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later use std::collections::HashMap; -use std::path::Path; use std::path::PathBuf; use super::PfrGlobalMassBalance; diff --git a/cmtool-core/src/grid/mod.rs b/cmtool-core/src/grid/mod.rs index e1efd17e..e2a216fd 100644 --- a/cmtool-core/src/grid/mod.rs +++ b/cmtool-core/src/grid/mod.rs @@ -227,7 +227,7 @@ pub trait CompartmentMeshManip { /// # Arguments /// /// * `cell_id` - The ID of the cell. - /// /// * `axis_project` - Axis index on which the surface is calculated + /// * `axis_project` - Axis index on which the surface is calculated /// /// # Returns /// diff --git a/cmtool-core/src/utils/area.rs b/cmtool-core/src/utils/area.rs index 1f1b2067..0483d897 100644 --- a/cmtool-core/src/utils/area.rs +++ b/cmtool-core/src/utils/area.rs @@ -24,7 +24,7 @@ fn sort_polygon_ccw(points: &[[f64; 2]]) -> Vec<[f64; 2]> { sorted } -fn project_points_to_plane_2d(points: &Vec<[f64; 3]>, normal: &CartesianVec3) -> Vec<[f64; 2]> { +fn project_points_to_plane_2d(points: &[[f64; 3]], normal: &CartesianVec3) -> Vec<[f64; 2]> { let n = normal.normalized(); let arbitrary = CartesianVec3::from_point_origin(if n.0[0].abs() < 0.9 { CartesianCoordinates([1.0, 0.0, 0.0]) @@ -42,7 +42,7 @@ fn project_points_to_plane_2d(points: &Vec<[f64; 3]>, normal: &CartesianVec3) -> .collect() } -fn sort_points_ccw_3d(points: &Vec<[f64; 3]>, normal: &CartesianVec3) -> Vec<[f64; 3]> { +fn sort_points_ccw_3d(points: &[[f64; 3]], normal: &CartesianVec3) -> Vec<[f64; 3]> { let projected = project_points_to_plane_2d(points, normal); let sorted_2d = sort_polygon_ccw(&projected); diff --git a/cmtool-data/Cargo.toml b/cmtool-data/Cargo.toml index a0c5c4ff..ee300d78 100644 --- a/cmtool-data/Cargo.toml +++ b/cmtool-data/Cargo.toml @@ -14,7 +14,7 @@ nalgebra.workspace = true nalgebra-sparse.workspace=true ndarray.workspace=true serde.workspace = true -serde_cbor.workspace = true +#serde_cbor.workspace = true serde_json.workspace = true thiserror.workspace = true diff --git a/cmtool-data/src/case.rs b/cmtool-data/src/case.rs index b76697d3..d1fe8e9f 100644 --- a/cmtool-data/src/case.rs +++ b/cmtool-data/src/case.rs @@ -2,7 +2,6 @@ use crate::{CMAExportType, DataError}; use serde::{Deserialize, Serialize}; -use std::ffi::os_str::Display; use std::io::{BufReader, Read, Write}; use std::{collections::HashMap, fs, path::Path}; @@ -58,8 +57,8 @@ impl std::fmt::Display for CMCase { impl CMCase { pub fn n_compartment(&self) -> u32 { - if self.n_div.iter().find(|e| **e == 0).is_some() { - return 1; + if self.n_div.contains(&0) { + return 1; //FIXME } self.n_div.iter().product() } @@ -127,13 +126,15 @@ impl CMCase { let ok_gas = if has_gas_volume && !has_gas_flow { false } else { - !(has_gas_flow && !has_gas_volume) + // !(has_gas_flow && !has_gas_volume) + !has_gas_flow || has_gas_volume }; let ok_liq = if has_liq_volume && !has_liq_flow { false } else { - !(has_liq_flow && !has_liq_volume) + // !(has_liq_flow && !has_liq_volume) + !has_liq_flow || has_liq_volume }; ok_liq && ok_gas diff --git a/cmtool-data/src/descriptors.rs b/cmtool-data/src/descriptors.rs index fb756027..a03b04cc 100644 --- a/cmtool-data/src/descriptors.rs +++ b/cmtool-data/src/descriptors.rs @@ -37,7 +37,7 @@ impl From for String { } impl From for PhaseCM { - fn from(value: String) -> Self { + fn from(_value: String) -> Self { todo!() } } diff --git a/cmtool-data/src/lib.rs b/cmtool-data/src/lib.rs index 671305cd..bf214b4c 100644 --- a/cmtool-data/src/lib.rs +++ b/cmtool-data/src/lib.rs @@ -51,11 +51,13 @@ pub enum DataError { } #[inline(always)] +#[allow(unused)] fn linear_index_row_major(_n_row: usize, n_col: usize, i: usize, j: usize) -> usize { i * n_col + j } #[inline(always)] +#[allow(unused)] fn linear_index_col_major(n_row: usize, _n_col: usize, i: usize, j: usize) -> usize { j * n_row + i } diff --git a/cmtool-data/src/transitioner/mod.rs b/cmtool-data/src/transitioner/mod.rs index 707d6072..a209f2c4 100644 --- a/cmtool-data/src/transitioner/mod.rs +++ b/cmtool-data/src/transitioner/mod.rs @@ -30,7 +30,7 @@ use buffer::{FlowMapBuffer, read_descriptors}; /// /// # Example /// -/// ```no_run +/// ```ignore /// let transitioner = MyTransitioner::from_case("root/path", &case)?; /// let state = transitioner.advance(12.0, 0.1); /// println!("Current state: {:?}", state); @@ -140,7 +140,7 @@ pub enum TransitionerType { /// /// # Example /// -/// ```no_run +/// ```ignore /// let transitioner = DiscontinuousTransitioner { /// state_buffer: vec![Arc::new(state1), Arc::new(state2), Arc::new(state3)], /// time_per_flomap: 0.5, @@ -267,7 +267,7 @@ impl FlowMapTransitioner for SimpleTransitioner { &self.state_buffer[self.current_index] } - fn need_advance(&self, _current_time: f64, time_step: f64) -> bool { + fn need_advance(&self, _current_time: f64, _time_step: f64) -> bool { true //Actually needs to be alsways updated because of remaining_time } diff --git a/cmtool-python/Cargo.toml b/cmtool-python/Cargo.toml index 47877b26..41ffc386 100644 --- a/cmtool-python/Cargo.toml +++ b/cmtool-python/Cargo.toml @@ -15,6 +15,6 @@ crate-type = ["cdylib"] [dependencies] cmtool-data.workspace = true -numpy = "0.24.0" +numpy.workspace = true pyo3.workspace = true -nalgebra.workspace=true +nalgebra.workspace = true diff --git a/cmtool-python/src/transitionner.rs b/cmtool-python/src/transitionner.rs index 63ebcd4e..62418a54 100644 --- a/cmtool-python/src/transitionner.rs +++ b/cmtool-python/src/transitionner.rs @@ -1,8 +1,7 @@ use std::sync::Arc; -use cmtool_data::{CCMCaseInfo, CMCaseReader, DiscontinuousTransitioner, FlowMapTransitioner}; +use cmtool_data::{DiscontinuousTransitioner, FlowMapTransitioner}; use numpy::PyArray1; -use numpy::ndarray::{self}; use pyo3::prelude::*; #[pyclass(name = "DiscontinuousTransitioner")] diff --git a/cmtool/src/main.rs b/cmtool/src/main.rs index d6b10e1c..1689f5b2 100644 --- a/cmtool/src/main.rs +++ b/cmtool/src/main.rs @@ -4,7 +4,6 @@ use cmtool::CmtoolError; use std::fmt::Write; use std::path::PathBuf; -use std::process::ExitCode; use std::{env, path::Path}; mod args; use args::*; diff --git a/examples/src/map_generation/mod.rs b/examples/src/map_generation/mod.rs index f86ff79e..2a66e64e 100644 --- a/examples/src/map_generation/mod.rs +++ b/examples/src/map_generation/mod.rs @@ -6,7 +6,7 @@ pub fn generate(reactor_input_file_name: &str) -> bool { eprintln!("{}", msg); return false; } - return true; + true } #[cfg(test)] From a9a148e8b2a53f966b7470df9ee18af1f6986b90 Mon Sep 17 00:00:00 2001 From: bcasale Date: Tue, 17 Feb 2026 11:15:53 +0100 Subject: [PATCH 21/44] chore: clippy --- cmtool-assemble/src/generators.rs | 2 ++ cmtool-assemble/src/parser/reactors.rs | 16 ++++++++-------- cmtool-core/src/ensight_gold/geo.rs | 11 +++++++---- cmtool-core/src/grid/mod.rs | 8 ++++---- cmtool-core/src/lib.rs | 14 +++++++------- cmtool-core/src/model/mod.rs | 1 + cmtool-python/src/transitionner.rs | 2 +- cmtool/src/main.rs | 4 ++-- 8 files changed, 32 insertions(+), 26 deletions(-) diff --git a/cmtool-assemble/src/generators.rs b/cmtool-assemble/src/generators.rs index 060a157a..71907fb2 100644 --- a/cmtool-assemble/src/generators.rs +++ b/cmtool-assemble/src/generators.rs @@ -32,7 +32,9 @@ pub struct Generator { } struct Field0D { + #[allow(unused)] name: String, + #[allow(unused)] value: f64, } diff --git a/cmtool-assemble/src/parser/reactors.rs b/cmtool-assemble/src/parser/reactors.rs index 1b323132..2633e721 100644 --- a/cmtool-assemble/src/parser/reactors.rs +++ b/cmtool-assemble/src/parser/reactors.rs @@ -82,14 +82,14 @@ pub fn parse_connection( ] } -fn convert_feed_flux_to_flux(feed: generated_domain::FeedFluxType) -> generated_domain::FluxType { - generated_domain::FluxType { - source: feed.source, - target: feed.target, - phase: feed.phase, - value: feed.value, - } -} +// fn convert_feed_flux_to_flux(feed: generated_domain::FeedFluxType) -> generated_domain::FluxType { +// generated_domain::FluxType { +// source: feed.source, +// target: feed.target, +// phase: feed.phase, +// value: feed.value, +// } +// } // pub fn parse_feed( // info: &DomainInfo, diff --git a/cmtool-core/src/ensight_gold/geo.rs b/cmtool-core/src/ensight_gold/geo.rs index 007d26d6..acc5f097 100644 --- a/cmtool-core/src/ensight_gold/geo.rs +++ b/cmtool-core/src/ensight_gold/geo.rs @@ -218,12 +218,15 @@ impl Geometry { reader.ignore_line()?; } - let node_id_choice = reader.get_line_string()?; + let (ignore_node_id, ignore_element_id) = { + let _node_id_choice = reader.get_line_string()?; - let ignore_node_id = false; //TODO find in node_id_choice: assign + let ignore_node_id = false; //TODO find in node_id_choice: assign - let element_id_choice = reader.get_line_string()?; - let ignore_element_id = false; //TODO find in element_id_choice: assign + let _element_id_choice = reader.get_line_string()?; + let ignore_element_id = false; //TODO find in element_id_choice: assign + (ignore_node_id, ignore_element_id) + }; // println!("{} {}", node_id_choice, element_id_choice); diff --git a/cmtool-core/src/grid/mod.rs b/cmtool-core/src/grid/mod.rs index e2a216fd..08d381dd 100644 --- a/cmtool-core/src/grid/mod.rs +++ b/cmtool-core/src/grid/mod.rs @@ -539,10 +539,10 @@ impl CompartmentMeshManip for MeshCylindrical { CylindricalAxis::Theta => delta_ijk[0] * delta_ijk[2], // ds = dr*dz CylindricalAxis::Z => { // ds =r*dr*dtheta - // R here is not radius but (r-R) - let R = self.axes[0].edges[points_indices[0] + 1]; + // rr here is not radius but (r-R) + let rr = self.axes[0].edges[points_indices[0] + 1]; let r2 = self.axes[0].edges[points_indices[0]]; - 0.5 * (R * R - r2 * r2) * delta_ijk[1] + 0.5 * (rr * rr - r2 * r2) * delta_ijk[1] } } } @@ -583,7 +583,7 @@ impl CompartmentMeshManip for MeshCylindrical { Some(mesh_id) } - fn is_point_inside(&self, cell_id: usize, point_coords: &Coords3) -> bool { + fn is_point_inside(&self, _cell_id: usize, _point_coords: &Coords3) -> bool { todo!() } diff --git a/cmtool-core/src/lib.rs b/cmtool-core/src/lib.rs index 12be5fb3..8dbb7644 100644 --- a/cmtool-core/src/lib.rs +++ b/cmtool-core/src/lib.rs @@ -20,19 +20,19 @@ pub enum ExportType { EnsightGold, } -trait GeometryInfo {} +// trait GeometryInfo {} -trait CfdCase { - fn get_root(&self) -> String; - fn get_geometry_relative_path(&self) -> String; -} +// trait CfdCase { +// fn get_root(&self) -> String; +// fn get_geometry_relative_path(&self) -> String; +// } mod errors; pub use errors::CoreError; pub struct CMHandle { model: Arc, - root_result: String, + _root_result: String, //TODO EITHER USE IT OR REMOVE eg_geometry: Arc, cm_geometry: Arc, } @@ -76,7 +76,7 @@ impl CMHandle { Ok(Self { model: Arc::new(CMModel::init(cm_geometry.clone())), - root_result: String::from("./test"), + _root_result: String::from("./test"), eg_geometry, cm_geometry, }) diff --git a/cmtool-core/src/model/mod.rs b/cmtool-core/src/model/mod.rs index a3b22571..7853dc2f 100644 --- a/cmtool-core/src/model/mod.rs +++ b/cmtool-core/src/model/mod.rs @@ -73,6 +73,7 @@ impl CMModel { model } + #[allow(unused)] fn compute_volume_integral_per_zone() -> Vec { todo!() } diff --git a/cmtool-python/src/transitionner.rs b/cmtool-python/src/transitionner.rs index 62418a54..57033969 100644 --- a/cmtool-python/src/transitionner.rs +++ b/cmtool-python/src/transitionner.rs @@ -47,7 +47,7 @@ unsafe impl Send for HydroStateWrapper {} #[pymethods] impl HydroStateWrapper { #[getter] - pub fn transition(&self, py: Python<'_>) -> (&[usize], &[usize], &[f64]) { + pub fn transition(&self, _py: Python<'_>) -> (&[usize], &[usize], &[f64]) { let t = unsafe { (*self.0).get_transition() }; (t.row_indices(), t.col_indices(), t.values()) } diff --git a/cmtool/src/main.rs b/cmtool/src/main.rs index 1689f5b2..ba7a8bd5 100644 --- a/cmtool/src/main.rs +++ b/cmtool/src/main.rs @@ -19,7 +19,7 @@ fn main() -> Result<(), CmtoolError> { AllModes::Cfd(cfdargs) => match cfdargs.mode { Mode::Auto(autoargs) => auto_main(cfdargs.common, autoargs), - Mode::Manual(manual_args) => todo!(), + Mode::Manual(_manual_args) => todo!(), }, AllModes::Xml(xml) => { let path = PathBuf::from(out_or_default(xml.out_dir)); @@ -55,7 +55,7 @@ fn auto_main(common: CommonArgs, autoargs: AutoArgs) -> Result<(), CmtoolError> #[cfg(feature = "use_vtk")] handle.write_vtk(format!("{}/{}/cma_case.vtu", root_dir, stem)); - return Ok(()); + let p1 = "/home/benjamin/Documents/thesis/cfd-cma/sanofi_cfd/inputs/RESULTS.scl1"; let p2 = "/home/benjamin/Documents/thesis/cfd-cma/sanofi_cfd/inputs/RESULTS.scl2"; let p3 = "/home/benjamin/Documents/thesis/cfd-cma/sanofi_cfd/inputs/RESULTS.scl3"; From bce387a0712c66ffd5b4a079226905807e14d7a5 Mon Sep 17 00:00:00 2001 From: bcasale Date: Tue, 17 Feb 2026 13:54:19 +0100 Subject: [PATCH 22/44] chore: minor --- cmtool-core/src/grid/collections.rs | 1 + cmtool-core/src/lib.rs | 26 +++++++-------- cmtool-cxx/src/lib.rs | 1 + cmtool-python/src/case.rs | 6 ++-- cmtool/src/main.rs | 49 ++++++++++++++++++++--------- 5 files changed, 52 insertions(+), 31 deletions(-) diff --git a/cmtool-core/src/grid/collections.rs b/cmtool-core/src/grid/collections.rs index f6abe85e..40189279 100644 --- a/cmtool-core/src/grid/collections.rs +++ b/cmtool-core/src/grid/collections.rs @@ -58,6 +58,7 @@ pub const fn cylindrical_index(axis: CylindricalAxis) -> usize { } #[inline(always)] +#[allow(unused)] //FIXME pub const fn index_to_oriented(axis: usize) -> OrientedAxis { match axis { 0 => OrientedAxis::I, diff --git a/cmtool-core/src/lib.rs b/cmtool-core/src/lib.rs index 8dbb7644..1e532365 100644 --- a/cmtool-core/src/lib.rs +++ b/cmtool-core/src/lib.rs @@ -1,21 +1,22 @@ // SPDX-License-Identifier: GPL-3.0-or-later -use std::{path::Path, sync::Arc}; - -use cmtool_data::{RawData, RawDataFlux, RawDataScalar}; - -use crate::model::{CMGeometry, CMModel, Scalar, Vector}; - -#[cfg(feature = "use_vtk")] -use crate::grid::vtk::VtkCm; -#[cfg(feature = "use_vtk")] -use crate::grid::vtk::add_celldata_to_vtk; - pub mod coordinates; pub mod ensight_gold; +mod errors; pub mod grid; pub mod model; pub mod utils; +pub use errors::CoreError; + +#[cfg(feature = "use_vtk")] +use grid::vtk::VtkCm; +#[cfg(feature = "use_vtk")] +use grid::vtk::add_celldata_to_vtk; + +use cmtool_data::{RawData, RawDataFlux, RawDataScalar}; +use model::{CMGeometry, CMModel, Scalar, Vector}; +use std::{path::Path, sync::Arc}; + pub enum ExportType { EnsightGold, } @@ -27,9 +28,6 @@ pub enum ExportType { // fn get_geometry_relative_path(&self) -> String; // } -mod errors; -pub use errors::CoreError; - pub struct CMHandle { model: Arc, _root_result: String, //TODO EITHER USE IT OR REMOVE diff --git a/cmtool-cxx/src/lib.rs b/cmtool-cxx/src/lib.rs index b79e6bf4..ca379376 100644 --- a/cmtool-cxx/src/lib.rs +++ b/cmtool-cxx/src/lib.rs @@ -51,6 +51,7 @@ mod ffi { fn has_gas(self: &IterationStateWrapper) -> bool; + #[allow(clippy::needless_lifetimes)] unsafe fn get_misc<'a>(self: &'a IterationStateWrapper, key: &str) -> &'a [f64]; fn has_misc(self: &IterationStateWrapper, key: &str) -> bool; diff --git a/cmtool-python/src/case.rs b/cmtool-python/src/case.rs index 253eab37..e9313e2c 100644 --- a/cmtool-python/src/case.rs +++ b/cmtool-python/src/case.rs @@ -6,7 +6,8 @@ use pyo3::prelude::*; use crate::PythonError; //Use C compartible enum because Enum-Struct not supported by PyO3 -#[pyclass(name = "CMExportType")] +// #[pyclass(name = "CMExportType")] +#[pyclass(from_py_object, name = "CMExportType")] #[derive(Clone, Copy)] pub enum CMExportTypeWrapper { LiquidFlow, @@ -31,7 +32,8 @@ impl From for cmtool_data::CMAExportType { } } -#[pyclass(name = "CMCase")] +// #[pyclass(name = "CMCase")] +#[pyclass(from_py_object, name = "CMCase")] #[derive(Clone)] pub struct CMCaseWrapper(cmtool_data::CMCase); diff --git a/cmtool/src/main.rs b/cmtool/src/main.rs index ba7a8bd5..76808353 100644 --- a/cmtool/src/main.rs +++ b/cmtool/src/main.rs @@ -1,8 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-or-later use cmtool::CmtoolError; -use std::fmt::Write; +use cmtool_data::{RawData, RawDataFlux}; +// use std::fmt::Write; +// use std::fs; use std::path::PathBuf; use std::{env, path::Path}; mod args; @@ -35,6 +37,15 @@ fn auto_main(common: CommonArgs, autoargs: AutoArgs) -> Result<(), CmtoolError> .and_then(|s| s.to_str()) .unwrap(); // Converts OsStr to &str + // let mut f = cmtool::check_flows( + // &RawDataFlux::read_raw("./out/cuve_sldmsh_initmrf/velocity.raw").unwrap(), + // ) + // .unwrap(); + + // println!("{}", f); + + // return Ok(()); + let root_dir = out_or_default(common.out); let case = cmtool_core::ensight_gold::case::Case::read(&autoargs.case_path)?; @@ -56,22 +67,30 @@ fn auto_main(common: CommonArgs, autoargs: AutoArgs) -> Result<(), CmtoolError> #[cfg(feature = "use_vtk")] handle.write_vtk(format!("{}/{}/cma_case.vtu", root_dir, stem)); - let p1 = "/home/benjamin/Documents/thesis/cfd-cma/sanofi_cfd/inputs/RESULTS.scl1"; - let p2 = "/home/benjamin/Documents/thesis/cfd-cma/sanofi_cfd/inputs/RESULTS.scl2"; - let p3 = "/home/benjamin/Documents/thesis/cfd-cma/sanofi_cfd/inputs/RESULTS.scl3"; - // - let export = handle - .dump_vector_from_scalar(format!("{}/flowL", root_dir), p1, p2, p3) - .unwrap(); - - if let Some(mut check_csv) = cmtool::check_flows(&export) { - check_csv - .write_str(&format!("sanitize_{}.csv", stem)) - .unwrap(); - }; - + let f = cmtool::check_flows( + &RawDataFlux::read_raw("./out/cuve_sldmsh_initmrf/velocity.raw").unwrap(), + ) + .unwrap(); + println!("{}", f); + // return Ok(()); Ok(()) + // let p1 = "/home/benjamin/Documents/thesis/cfd-cma/sanofi_cfd/inputs/RESULTS.scl1"; + // let p2 = "/home/benjamin/Documents/thesis/cfd-cma/sanofi_cfd/inputs/RESULTS.scl2"; + // let p3 = "/home/benjamin/Documents/thesis/cfd-cma/sanofi_cfd/inputs/RESULTS.scl3"; + // // + // let export = handle + // .dump_vector_from_scalar(format!("{}/flowL", root_dir), p1, p2, p3) + // .unwrap(); + + // if let Some(mut check_csv) = cmtool::check_flows(&export) { + // check_csv + // .write_str(&format!("sanitize_{}.csv", stem)) + // .unwrap(); + // }; + + // Ok(()) + // cmtool::check_flows(&RawDataFlux::read_raw( // "./out/cuve_sldmsh_initmrf/axial_velocity.raw", // ).unwrap()); From e66b4f4898de4f5b648c599f7e175d69f2a8ab9e Mon Sep 17 00:00:00 2001 From: bcasale Date: Wed, 18 Feb 2026 16:01:25 +0100 Subject: [PATCH 23/44] doc: improve crate documentation --- CHANGELOG.md | 25 + documentations/docbook/book.toml | 23 +- documentations/docbook/src/SUMMARY.md | 3 + .../docbook/src/assets/cm_assemble.svg | 4 + .../docbook/src/assets/tuto_mixing.svg | 1788 +++++++++++++++++ documentations/docbook/src/cfd_to_cma.md | 5 +- documentations/docbook/src/context.md | 13 +- documentations/docbook/src/datatype.md | 20 +- documentations/docbook/src/getting_started.md | 63 + documentations/docbook/src/modeler.md | 17 + documentations/docbook/src/refs.bib | 75 + .../docbook/src/tuto/basic_mixing.md | 21 +- documentations/docbook/src/tuto/basic_xml.md | 48 + examples/data/case_0d1d_liq/reactors.xml | 22 +- examples/data/simple_0d/reactors.xml | 4 +- examples/python/mixing_simple.py | 16 +- pyproject.toml | 6 + uv.lock | 411 +++- 18 files changed, 2508 insertions(+), 56 deletions(-) create mode 100644 documentations/docbook/src/assets/cm_assemble.svg create mode 100644 documentations/docbook/src/assets/tuto_mixing.svg create mode 100644 documentations/docbook/src/getting_started.md create mode 100644 documentations/docbook/src/refs.bib create mode 100644 documentations/docbook/src/tuto/basic_xml.md diff --git a/CHANGELOG.md b/CHANGELOG.md index f032efac..0e2c3c98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,31 @@ ## 0.1.4 *Date: * +#### Features: + +- Assemble flowmaps + + +#### General Enhancements + +- Improve transitioner api and performance +- Improve documentation +- Improve python bindings + +#### Build System Changes + +#### Breaking Changes + +#### Deprecations +- C Case Reader + +#### Bug Fixes + +#### Known Issue + +- CFD generated model does not compute flows correctly + + ## 0.1.3 *Date: 11/23/2025* diff --git a/documentations/docbook/book.toml b/documentations/docbook/book.toml index 0cf84926..2d02a276 100644 --- a/documentations/docbook/book.toml +++ b/documentations/docbook/book.toml @@ -3,11 +3,26 @@ authors = ["Casale Benjamin"] language = "en" src = "src" title = "Compartment Modeling Tool" -heading-split-level = 4 - -[output.html] -mathjax-support = true +# heading-split-level = 4 [preprocessor.toc] command = "mdbook-toc" renderer = ["html"] + +[preprocessor.bib] +bibliography = "refs.bib" +backend = "csl" +csl-style = "apa" +title = "References" + + +[output.html] +mathjax-support = true + + +[output.html.playground] +editable = false +copyable = true +copy-js = true +line-numbers = false +runnable = false diff --git a/documentations/docbook/src/SUMMARY.md b/documentations/docbook/src/SUMMARY.md index 32c7cff9..a838b32a 100644 --- a/documentations/docbook/src/SUMMARY.md +++ b/documentations/docbook/src/SUMMARY.md @@ -1,6 +1,7 @@ # Summary - [Home Page](./main.md) +- [Getting Started](./getting_started.md) - [Context](./context.md) - [Components](./components.md) - [Data type](./datatype.md) @@ -8,5 +9,7 @@ - [CFD-To-CMA](./cfd_to_cma.md) - [Tutorial]() + - [Basic Compartment Model](./tuto/basic_io.md) - [Basic mixing example](./tuto/basic_mixing.md) + - [Write XML Compartment Model Case](./tuto/basic_xml.md) diff --git a/documentations/docbook/src/assets/cm_assemble.svg b/documentations/docbook/src/assets/cm_assemble.svg new file mode 100644 index 00000000..0fec5f8b --- /dev/null +++ b/documentations/docbook/src/assets/cm_assemble.svg @@ -0,0 +1,4 @@ + + +eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1cXGlT2zpcdTAwMTf+3l/BcL+WXFytR1K/XHUwMDA1XHUwMDAyaVgua8Pyzlx1MDAxZCYkhqQ4cUjMXHUwMDEy7vS/v7JcdTAwMDHLO1x0doBOYzqdWLJk2TrPc1x1MDAxNlx1MDAxZPm/L0tLy+5kaC1/W1q2XHUwMDFl2i2711x1MDAxObXul7965XfWaNxzXHUwMDA2uor452PndtT2r+y67nD87e+/TYtK2+k/tbJsq29cctyxvu5/+nxp6T//f13T63ht++LnpuXI86PbxkNcclx1MDAxZVF/3D098Zv6XHUwMDE3vVxmZmS13dbgyrZM1YMuZ4hcdTAwMDbnXHUwMDEzfU4pXHUwMDBmzu97XHUwMDFkt6vLsFBBWdfqXXVd71x1MDAxMbhcZlxun/r9toSCkrE7cq6tNcd2Rt7N/8KW92dufdFqX1+NnNtBJ7jGXHUwMDFktVx1MDAwNuNha6Sf1Vxcd9mz7UN34veu35d+N8uxe1x1MDAxYz9cdTAwMGaSxMqzWumbXnVcdTAwMDfW2HufOCh1hq12z/VeXHUwMDAwRuYpvFx1MDAxMVx1MDAwZVx1MDAxYlx1MDAxZP/V/2vGNGr1rYb37lx1MDAwN7e2XHUwMDFkXHUwMDE091x1MDAwNlx1MDAxZMt7o8stXHUwMDE0udug83y3l3kzk0KfS36ZsVuW1zGWoDhBXHUwMDAwZnKM8NBE4T/OwJcjTJFcdTAwMDRJXHUwMDAwXHUwMDBiM6pxTVx1MDAwYpDr93rZsseWmVx1MDAwMm9o63HhXG5cdTAwMGJYSMj2XHUwMDBm1vjlbr9t7ZO1Y7d/1nSrYit4zIigtUYj5345qPn1Na9faW/z9kNcdTAwMDNdsGq3tbZ3vvZj9XGlhH7P+qPB2arqNLvi6PtdrdnnV+yshH5P77Y27If1+uP1dq1h1SePe9DqTtfv8y8jR7fDTutpYrBcdTAwMTCYXHUwMDExJrFcdTAwMDaVmTq7N7iOXHUwMDBime20r81cXH5cdFxyOMZcbkPc3JhcdTAwMWPsNC/weZc210Rt21x1MDAxZLSnZlx1MDAwNUFccpx8UIQk6oVcdTAwMTUoTmFcdTAwMDUmzIVcdTAwMGJWiLBcdTAwMDIuzFxuQlx1MDAwMJJcdTAwMThCXCJiSIFDJilcYoJcdTAwMDTMhVx1MDAxNDZcdTAwMWH0bFxiN+div+Nsb/ZO1q5uhiWAXGbLg7s9uD88dJuHO2KtWa+Px7yEfnfubsQx/Lx+YEetOsU3R/260yuhX3Jwc+5utjb4oGufsJtcdTAwMTXn9Gx3XFxCv+LHbnW3tltcdTAwMWb1XHUwMDBmXHUwMDFjl463rs9cdTAwMWbXNksjXHUwMDFiwoGWRDZ8UmPrTte6vr/a3Vb1i9FPtXUxNdlcdTAwMDA1iPNNXHUwMDEwYmT5hWyYXHUwMDAxsrFAwLRbcE2Ea0hhruFYYcZcYjbWYIhrSLzwhWtASKqEXHUwMDEyplVJVFPATnhcdTAwMWRcblx1MDAwMiRQKFx1MDAwN1xu/1xc2yftzulBvXG4et24ZeNcdTAwMWb22X1cdTAwMTJcbvpcdTAwMWVxlUuiKpdglEBcdTAwMDHFOFx0g1x1MDAwNVxiMkBAp1x1MDAwNVx1MDAwMclcdTAwMDKBZlx1MDAxOIx52NBcdLlwmVxul1xuylx1MDAwNOH0LSCIPEpCUIFLXCLZXGaCXHUwMDFhXHUwMDE0XHUwMDBmnV5cdTAwMWNl5teSkVx1MDAxOP8k+P3v19SrQ2LoXHUwMDFkK9g0SIBt7LZG7qqelN7gKj5Wa9DJqPFbVT0gd61WYop1u8y6oWNPrvxcdHpccqm2Pblr11x1MDAxZlx1MDAxYVx1MDAxM7F//nP/rLqDhrX9KZFaXHUwMDAxzqUmSE5cYpIxXHUwMDA3XHUwMDFhQYVShlx1MDAxMddcdTAwMGVcdTAwMWNcIka2XHUwMDE3IH5cdTAwMGKIWWFcdTAwMTAzTCUliptcdTAwMDFcdTAwMWFcdTAwMTCzhClcdTAwMWRYzVIqKoHxN6myXHUwMDFjZVx1MDAwM4B5aCxcdTAwMGJcZlx1MDAxN8HwOb7aXHUwMDFinzZvdqpbXHUwMDBmrl1tws/26o/pMIwqTM+FwNpYUYRcdTAwMWI58OSWIVxcQXr+XHSmSuthyZIxsVx1MDAwNYpnQTEvjGKsXHUwMDE0UCapSDNIIdMglZiBnl54k++bi2KqwFx1MDAwNEBcdTAwMTcoLoLizaN6c6xcdTAwMDZccvt+XHUwMDAyt/Y+c+n51t50KGZcdTAwMTXCiFx1MDAwNIkpYlrzRlDMXHUwMDExrWhcdTAwMDArpjTOQVxio6dcdTAwMTcgflx1MDAwYoihOIgpRUJcbirTwtoyXHUwMDEzxFx1MDAxOIOGv8Zd+bpYqoU9XVx1MDAxMorXhVtcdTAwMDX62G6uy2p1sElcdTAwMWa5q7amQbFcZlx1MDAwNVx1MDAxOZ6CzUl1a4Q3XHUwMDE0ajZwXyA1glRR3GhGQlx1MDAxMaIg1fPN1rZcdTAwMWNxjJg0rUrzfIVEn8BqjlxcrVx1MDAwNfC3XHUwMDA06lx0c/dcdTAwMGX2bbKxsYVU7+Z4e1JcdTAwMWLfTVx1MDAwM1RFoFwiKKJcXKtU5PlHMdhCXHUwMDA1Y4FcdTAwMDBJIHrSUozmXHUwMDA1imdBsSyOYn/dR+E0dctcdTAwMTLQXHUwMDBlgriIXHUwMDEwriBkL5WkbSWT0lxixVx1MDAwMsNcdTAwMDUwnL9cdTAwMWVcdTAwMWaLhUdATFQ0zkxFMs6MkZkmY1x1MDAxN1dcdTAwMTBT5uAhfL9cdTAwMGVcXFx1MDAwYlFM8Vx1MDAxZlx1MDAwMlxcVVx1MDAxOLhKau/Sy1x1MDAwMUlTv4lAVmAnM0455kqZK0rTv0qQT+Dvhlx1MDAwNNM7VpIyWVx1MDAwNppDXHUwMDEz9Zx81ZhcItNKX913Or7IOaOLnlx1MDAxYq647D1YnT3vTURehH7iiue56knDklNGkKHqtFpcdTAwMDRcIvF8v6LPNy3vxKnBsi+c+6lWuPKX4vKoXHUwMDA3OKpcdTAwMDAn+lBIUCGNPHnAXHUwMDAzwFx1MDAxNYmBYsEkYZgkM05whVx1MDAxMiqYXHUwMDEyilxuXCKQYMZ0XGJYXG5ktFx1MDAxM6PEXHUwMDE2JFx1MDAxNSGpamGS0u9fXCLJWFpgXbB44Vx1MDAwYklcdNA2icCkfFx1MDAxZkEwzOQsy7hz4qhMKfWOlYSAzpOx8lx1MDAxMzPeyFhcdTAwMWOhSLxcIlx1MDAxOa54d07Co1x1MDAxM3F4sF6rN9mkKrd2XHUwMDFm3J1hSlx1MDAwZWzSpVx1MDAwMVx1MDAxZc09XHUwMDAxMIJcdTAwMWLQTkjxmeSTXHUwMDA1r6TzymrxKCFcdTAwMDOmXHRC8LTgQ9KXeSFcdTAwMTaCKFx1MDAwMsVcdTAwMTCdXHUwMDA3s4BcZoVCPopZVkKS6Fx1MDAxZMQ0eDt1vLvr0v7+fe+eVtcvuqfo+HB1c31wcLk5XHJWmYxj1eAywGpcdTAwMTKpXHUwMDBiXHUwMDEzIFx1MDAwYqprxaGKtf1NMFx1MDAxMqlQRfHCwFFcdTAwMDGMXHUwMDA1pYKX76hcYm1fzpTLNScjIKLzy1Hy747Ulc371thef+j9rG83uydHRzV7lUylVVnUtKdkqlgg5kb5LqBcdTAwMWGBaq2EtTetVDXq0pLHsYxcdTAwMTdcdTAwMDbWOlx1MDAxMYJcdTAwMTGq3lx1MDAxMlx1MDAwYnxcdTAwMDWogDD+eJ1cdTAwMWG5ekVL4G+J1PxM/Fxcn5xFXHUwMDE3zXFKOFCmYbWCvdw3goAgJDTnLlxm4lxm6K5cdTAwMTeGLlx1MDAwNaFcdTAwMDRFqbnYNFvJUiyJ/lx1MDAwN+XnoVxuqT3tWfZcdTAwMGXMXHUwMDA3uzJcdTAwMDbehEiWgeUs1zp/g9XSXHUwMDFig4FIcEFcdFAkJVx1MDAxN6HQSmp1ylx1MDAwM87H81x1MDAwZcr7zp2103tcdTAwMWH8+Ljndp9FZlxuisrf1JNHUVx1MDAxOMWcdM6TXHUwMDFiREJcdTAwMWFq4aO/RklcdTAwMWIlWFx1MDAxM4BcdTAwMTmjqSuLJHNlXHUwMDExK640g0j1lnS834KSVCR+vzJcdTAwMDNcdTAwMDNcdTAwMTXmmd5g3OtYU1x1MDAxMU2MVlx1MDAxNEGUckaUXHUwMDAw5SW85rBKSWbOO/BN/ma/PL6RwCpUaYHSbqogsf3xXHUwMDAwvFwiXGJcYuRt8FCCJ1cptFxuijRPW0tFXHUwMDE1onlcXFx0XHUwMDA2XGZcdTAwMTGmu6OzZEH8UVxcVS+BqyThXGIrlppcdTAwMDaRvW9WW12AqcBziCcqyrlcdTAwMTnNh8VcdTAwMTOzRdVvn1x1MDAxNNIyXGaqubFGXHUwMDBlXHUwMDE57KH920uwh8jZYienP3aazumJlSSDlNxEmGJ9IInvULB4XHUwMDAx51xinL9cdTAwMTeGM1x1MDAwN1xmNLxcdTAwMDZcdTAwMTXOIc5cdTAwMDKzXHUwMDEwXGKxOURcdTAwMWKVhs3HJ1x1MDAxMK9Ew42gPlx1MDAxNqdvXGZi5H+zI09jM1x1MDAxYVxyYkAoXHUwMDFlXHUwMDFkqGXtPXGmsNTeXHUwMDEzQlx1MDAxY1JyXHUwMDA3JKogpC0griTn4U1ar2OYXFwqi7E/XHUwMDA0w41cdTAwMTJ282BMmNY8aSpcdTAwMTlnb1x1MDAwNPBST/X8qfJDXHUwMDFhUr9cdTAwMTf+XHSQnCmmfm1cXELLwPn7ZjtcdTAwMDFnVOlHYF7iiEIxT0RSxlx1MDAwMSlcdTAwMDRcdTAwMWNzLlQym+vdM1x1MDAwYvI/+JNHSpzGv6yVjFqkmFx1MDAwZcrAc0E7XHUwMDEx2tksTjuMU6VlLJn77Fdm0o6GIVx1MDAxN5LQ8ldBJGZMfPy+hphcdTAwMDGhQlxyXiOWwvQxQ9xcdTAwMDJ5rjfhoOeQaJ+E0Ji/glx1MDAxMJWCK5BKXHUwMDEx/W5cdTAwMTHkXHUwMDA1SD/cKcn/bNArXHUwMDEx0eiGZcxVRT+t99lcdTAwMDGsfV2GkplcdTAwMTEqdVx1MDAxMSfSSIhZPun3R3HPVmHukVQwLFx1MDAxMUpdf822eFx1MDAxONKUXHUwMDE1Wfspj3q0/fVcdFwipvFFnLhIXHUwMDE2p6K5LNVoXHUwMDBiVjuVTHJcboibvatPlUhK4n1yQlx1MDAxYqtKz3rKqvIn4qH8z4zl8ZBC0b0kXHUwMDAwycVjVeFcdTAwMThcdTAwMTh//lx1MDAwM5JcdTAwMTYtWZg8XHUwMDE5tLNdfCdcdFx1MDAwNSpUhsWTuZNEm+dSXHUwMDExLOfAOrN+dm5OXHUwMDA2T6ZY+rUwg1x1MDAwMfRJlo4/Niv7y/NcdTAwMGJablxyh4eunvLg8Zfvetb9alx1MDAxMqR/XfqH96E2f2BcdTAwMWVALV+4f3359X9a81jmIn0= \ No newline at end of file diff --git a/documentations/docbook/src/assets/tuto_mixing.svg b/documentations/docbook/src/assets/tuto_mixing.svg new file mode 100644 index 00000000..e0eb4823 --- /dev/null +++ b/documentations/docbook/src/assets/tuto_mixing.svg @@ -0,0 +1,1788 @@ + + + + + + + + 2026-02-18T11:07:45.892998 + image/svg+xml + + + Matplotlib v3.10.8, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentations/docbook/src/cfd_to_cma.md b/documentations/docbook/src/cfd_to_cma.md index 776a70a9..eca536ba 100644 --- a/documentations/docbook/src/cfd_to_cma.md +++ b/documentations/docbook/src/cfd_to_cma.md @@ -1,6 +1,3 @@ # CFD-To-CMA -## References - -- Ensight gold specifications : - - https://dav.lbl.gov/archive/NERSC/Software/ensight/doc/Manuals/UserManual.pdf \ No newline at end of file +According to @@computational_engineering_international_inc_ensight_2003, ... diff --git a/documentations/docbook/src/context.md b/documentations/docbook/src/context.md index 06ddf69b..6c1840db 100644 --- a/documentations/docbook/src/context.md +++ b/documentations/docbook/src/context.md @@ -8,20 +8,17 @@ For these reasons, simulating an entire process at an industrial scale is not st One method to reduce complexity is the Compartment Modeling Approach (CMA). -## Compartment Modeling Approach +## Compartment Modeling Approach + One method to reduce complexity is the **Compartment Modeling Approach** (CMA). -The idea is simple: if we have a very slow and long-driven reaction that does not significantly affect the reactor's spatial properties (such as flow, temperature, pressure), we can first solve the main reactor's behavior with very high spatial and temporal precision but for a small time range, like a typical CFD study (covering minutes, or perhaps a few hours, of physical time). We then keep these results and reduce the spatial resolution as needed by projecting the results onto a coarser grid. These fixed results are used for long-term simulations. + +Inspired by scaled-down reactors, @@delafosse_cfd-based_2014 , the idea is simple. For a slow and long-driven reaction and that does not significantly affect the reactor's spatial properties (such as flow, temperature, pressure), one can first solve the main reactor's behavior with very high spatial and temporal precision but for a small time range, like a typical CFD study (covering minutes, or perhaps a few hours, of physical time). Then keep these results and reduce the spatial resolution as needed by projecting the results onto a coarser grid. These fixed results are used for long-term simulations. The specificity of CMA is that we reduce the results such that we no longer consider individual "cell meshes" but rather compartments that represent large spatial regions in the reactor, which are considered homogeneous. Furthermore, while meshes can be unstructured with cells of any shape and size, compartments are arranged in a structured grid of regular tetrahedrons. ### Use cases -This obsviouly reduce results accuracy but for some specific application it can be very useful. Compartments keeps, the main average flow between reactors regions (very important for mixing study), the eulerian scalar fields (gas-fraction,temperature,turbulency) is kept but volume averaged. - -Follow some academic work - - - +Even tough results accuracy is reduced, it can be very useful for some specific application. Compartments keeps, the main average flow between reactors regions (very important for mixing study @@delafosse_eulerlagrange_2015 ), the eulerian scalar fields (gas-fraction,temperature,turbulency) is kept but volume averaged usefull for segregated reactors @@hristov_simplified_2004. diff --git a/documentations/docbook/src/datatype.md b/documentations/docbook/src/datatype.md index 124dfc01..0b87ace7 100644 --- a/documentations/docbook/src/datatype.md +++ b/documentations/docbook/src/datatype.md @@ -22,20 +22,20 @@ Flows are stored as binary data with a structure that can be described as "flow- The raw file structure is as follows: -- Header: - - Number of compartments - - Number of flows -- Body: - - List of flows: +|Offset| Size | Type | Description| +--::--- | :----: | ---- | ----------- +0x00 | 4 | uint32 | Number of compartments +0x04 | 4 | uint32 | Number of flows (n) +0x08 | $$ n \times 2 \times 8 $$ | binary | Body (compartment & flow data) + ## Scalars Scalar fields are more standard files, they contains the value of the considered sclar for each zone of our grid, id for each compartment. -- Header: - - Number of compartments -- Body: - - List of values: - +|Offset| Size | Type | Description| +--::--- | :----: | ---- | ----------- +0x00 | 4 | uint32 | Number of compartments (m) +0x08 | $$ m \times 8 $$ | binary | Body (scalar values) ## Case diff --git a/documentations/docbook/src/getting_started.md b/documentations/docbook/src/getting_started.md new file mode 100644 index 00000000..2de04d18 --- /dev/null +++ b/documentations/docbook/src/getting_started.md @@ -0,0 +1,63 @@ +# Getting started + +Select the lastest available tag +```sh +git clone --depth 1 --branch git@github.com:Benncs/rcmtool.git +``` + + +## Rust + +To build the full cli application + +```sh +cargo build --release +``` + +Some example are available with : +```sh +cargo run --example +``` + +### VTK + +The VTK features can be enabled if VTK dev dependencies are installed in the system. +For rust build the feature 'use_vtk' is needed. +```sh +cargo build --release --features use_vtk +``` + +## Python package + +[uv](https://docs.astral.sh/uv/) package manager is highly recommended. + +To enable use of python package, the following + +```sh +uv venv +uv pip install -r pyproject.toml --extra examples +``` + + +Example can be run with +```sh +export EXAMPLE_ROOT='/path/to/cma/case' +uv run examples/python/mixing_simple.py +``` + +> **Note:** The `export` command works only on POSIX-compliant systems (Linux, macOS, Unix). +> Windows users should use `set` (Command Prompt) or `$env:` (PowerShell) instead. + + +### VTK + +The VTK features can be enabled if VTK dev dependencies are installed in the system. +Python with automatically detects + +## C++ + +Shared library object can be compiled with [meson](https://mesonbuild.com/) build system. +The best way is to use this folder as subproject and add the following to your 'meson.build' +```meson +cmtool = dependency('rcmtool', required: true, version: '>=0.1.0', static: true) +``` diff --git a/documentations/docbook/src/modeler.md b/documentations/docbook/src/modeler.md index 8b14d9c4..2293fabd 100644 --- a/documentations/docbook/src/modeler.md +++ b/documentations/docbook/src/modeler.md @@ -1,2 +1,19 @@ # Modeler +![CMA Philosophy](./assets/cm_assemble.svg "CMA Philosophy") + +**CMTool** has been designed to allow to reuse and assemble different compartment model. THis is either be to model from stach scale-down experiments or assemble CFD-based compartment model. The philosophy is to designed reactors with input/output position and flow for gas and liquid, assemble as desired in any direction. + +## Building Flowmaps with XML + +To assemble flowmaps almost seamlessly, compartment models are defined using an **XML file**. + +Each XML file must contain the following core elements: + +| Tag | Description | +|:----|:------------| +| **Reactors** | Defines individual reactor units. Different tags exist for **0D**, **1D**, **2D**, or pre-existing (**CFD-based**) reactors. | +| **Connections** | Specifies **intra-flowmap**, connections between reactors (liquid and/or gas phases).| +| **Feeds** | Defines input flows entering the domain from outside (liquid and/or gas) | + +More details [here](./tuto/basic_xml.md) diff --git a/documentations/docbook/src/refs.bib b/documentations/docbook/src/refs.bib new file mode 100644 index 00000000..be0629fc --- /dev/null +++ b/documentations/docbook/src/refs.bib @@ -0,0 +1,75 @@ + +@article{morchain_dynamic_2024, + title = {A dynamic compartment model for spatially heterogeneous reactors: {Scalar} and {Monte}-{Carlo} particle mixing}, + volume = {205}, + issn = {02638762}, + shorttitle = {A dynamic compartment model for spatially heterogeneous reactors}, + url = {https://linkinghub.elsevier.com/retrieve/pii/S026387622400217X}, + doi = {10.1016/j.cherd.2024.04.014}, + language = {en}, + urldate = {2024-10-09}, + journal = {Chemical Engineering Research and Design}, + author = {Morchain, Jérôme and Mayorga, Carlos and Villedieu, Philippe and Liné, Alain}, + month = may, + year = {2024}, + pages = {628--639}, +} + +@article{delafosse_eulerlagrange_2015, + title = {Euler–{Lagrange} approach to model heterogeneities in stirred tank bioreactors – {Comparison} to experimental flow characterization and particle tracking}, + volume = {134}, + issn = {00092509}, + url = {https://linkinghub.elsevier.com/retrieve/pii/S0009250915003851}, + doi = {10.1016/j.ces.2015.05.045}, + language = {en}, + urldate = {2024-09-18}, + journal = {Chemical Engineering Science}, + author = {Delafosse, Angélique and Calvo, Sébastien and Collignon, Marie-Laure and Delvigne, Frank and Crine, Michel and Toye, Dominique}, + month = sep, + year = {2015}, + pages = {457--466}, +} + +@article{hristov_simplified_2004, + title = {A {Simplified} {CFD} for {Three}-dimensional {Analysis} of {Fluid} {Mixing}, {Mass} {Transfer} and {Bioreaction} in a {Fermenter} {Equipped} with {Triple} {Novel} {Geometry} {Impellers}}, + volume = {82}, + copyright = {https://www.elsevier.com/tdm/userlicense/1.0/}, + issn = {09603085}, + url = {https://linkinghub.elsevier.com/retrieve/pii/S0960308504704034}, + doi = {10.1205/096030804322985281}, + language = {en}, + number = {1}, + urldate = {2024-09-16}, + journal = {Food and Bioproducts Processing}, + author = {Hristov, H.V. and Mann, R. and Lossev, V. and Vlaev, S.D.}, + month = mar, + year = {2004}, + pages = {21--34}, +} + +@article{delafosse_cfd-based_2014, + title = {{CFD}-based compartment model for description of mixing in bioreactors}, + volume = {106}, + issn = {00092509}, + url = {https://linkinghub.elsevier.com/retrieve/pii/S0009250913007690}, + doi = {10.1016/j.ces.2013.11.033}, + language = {en}, + urldate = {2025-12-10}, + journal = {Chemical Engineering Science}, + author = {Delafosse, Angélique and Collignon, Marie-Laure and Calvo, Sébastien and Delvigne, Frank and Crine, Michel and Thonart, Philippe and Toye, Dominique}, + month = mar, + year = {2014}, + pages = {76--85}, +} + +@book{computational_engineering_international_inc_ensight_2003, + address = {Apex, NC, USA}, + edition = {Version 7.6}, + title = {{EnSight} {User} {Manual}}, + url = {https://dav.lbl.gov/archive/NERSC/Software/ensight/doc/Manuals/UserManual.pdf}, + abstract = {Comprehensive user manual for EnSight visualization software, covering input/output, parts, variables, GUI, menus, features, modes, data formats, utility programs, and rendering techniques.}, + publisher = {Computational Engineering International, Inc.}, + author = {{Computational Engineering International, Inc.}}, + year = {2003}, + file = {PDF:/home-local/casale/Zotero/storage/FHCI7CI2/Computational Engineering International, Inc. - 2003 - EnSight User Manual.pdf:application/pdf}, +} diff --git a/documentations/docbook/src/tuto/basic_mixing.md b/documentations/docbook/src/tuto/basic_mixing.md index b86b7cae..745e89e4 100644 --- a/documentations/docbook/src/tuto/basic_mixing.md +++ b/documentations/docbook/src/tuto/basic_mixing.md @@ -1,10 +1,11 @@ # Basic Mixing Example -This example demonstrates how to use Cmtool as a Compartmental Modeling (CM) framework to perform realistic simulations. We focus on reactor mixing, a fundamental application of compartmental modeling. +This example demonstrates how to use Cmtool as a Compartmental Modeling (CM) framework to perform realistic simulations. We focus on reactor mixing, a fundamental application of compartmental modeling. ## Motivation -Mixing is one of the simplest applications of Compartmental Modeling, involving only the flows between compartments. As established in previous research (TODO: add references), mixing does not alter the total system mass. + +Mixing is one of the simplest applications of Compartmental Modeling, involving only the flows between compartments, mixing does not alter the total system mass. Following example was established in previous research @@morchain_dynamic_2024, but with a totally different implementation. The rate of change of mass in each compartment can be described by the following differential equation: @@ -25,28 +26,34 @@ This formulation, allows for efficient computation and integration over time, ma The mixing experiment involves integrating this ordinary differential equation (ODE) over time. An important aspect is that the matrix F can be time-dependent, denoted as F(t). **CMTool** simplifies this process with the **FlowMapTransitioner** trait.This trait allows users to load their cases, whether transient (with multiple flow maps) or not. The flowmap transitioner (abbreviated *fmt* ) is responsible for handling transitions between different flow maps and will automatically update the flow map over time as needed. +## Results +![Obtained with python code](../assets/tuto_mixing.svg "Obtained with python code") ## Python Code -Our Python wrapper offers an easy-to-use interface for out-of-the-box simulations and proofs of concept. With compatibility with the numpy API, performing calculations becomes straightforward. Currently, one type of transitioner is available, and the volumes and transitions matrix can be easily accessed from the iterator. Compatibility with sparse matrices ensures efficient integrations. Furthermore, this setup is compatible with the widely-used `solve_ivp` function for performant ODE integration, although more sophisticated methods can be envisioned. +Our Python wrapper offers an easy-to-use interface for out-of-the-box simulations and proofs of concept. With compatibility with the **numpy** API, performing calculations becomes straightforward. Currently, one type of transitioner is available, and the volumes and transitions matrix can be easily accessed from the iterator. Compatibility with sparse matrices ensures efficient integrations. Furthermore, this setup is compatible with the widely-used `solve_ivp` function for performant ODE integration, although more sophisticated methods can be set up. ```python -{{#include ../../../example/python/mixing_simple.py}} +{{#include ../../../../examples/python/mixing_simple.py}} ``` + + ## Rust Code -Here is an example of how to implement this in Rust. The **CMTool** components are compatible with popular linear algebra libraries like Nalgebra and Ndarray. Although the interface is currently low-level, it effectively demonstrates how to use CMtool for performing integrations. This example uses a first-order explicit scheme for simplicity. +Here is an example of how to implement this in Rust. The **CMTool** components are compatible with popular linear algebra libraries like **Nalgebra** and **Ndarray**. Although the interface is currently low-level, it effectively demonstrates how to use CMtool for performing integrations. This example uses a first-order explicit scheme for simplicity. -*Note*: For mixing problems, an implicit method is generally recommended for better performance and stability. Using Rust is advisable for building complete simulation tools. Rust allows developers to focus on writing performant code and provides more control over data, although it may require more development effort. +> *Note*: For mixing problems, an implicit method is generally recommended for better performance and stability. + + ```rust -{{#include ../../../example/src/mixing_simple.rs}} +{{#include ../../../../examples/src/mixing_simple.rs}} ``` diff --git a/documentations/docbook/src/tuto/basic_xml.md b/documentations/docbook/src/tuto/basic_xml.md new file mode 100644 index 00000000..3655d8e6 --- /dev/null +++ b/documentations/docbook/src/tuto/basic_xml.md @@ -0,0 +1,48 @@ +# XML + +## How to use + +- Rust API: `cmtool-assemble` exposes `Parser` struct as well as `generate_domain` +- App: rcmtool: + ```sh + cargo run -- xml --help + ``` +- Python API: TBD + +- Example: + - Names + - case_0d1d + - simple_0d + - simple_1d + - neubauer + - Run with + ```sh + cargo run --example + ``` + + +For Rust and Python API, please refer to RustDoc [Here](TBD) + + +## XML details + +### XML tags + +- Reactors: + - Reactor0D: + - Volume fraction + - Diameter/Height or volume + - Reactor1D: + - + - ReactorFromFile + - Path to file +- Connections: + - flux +- Fleeds + - Flux + +Different simple examples [are available](../../../../examples/data) to assemble flowmaps and scalar fields such as this one : + +```xml +{{#include ../../../../examples/data/case_0d1d_liq/reactors.xml}} +``` diff --git a/examples/data/case_0d1d_liq/reactors.xml b/examples/data/case_0d1d_liq/reactors.xml index a5965e59..284e4eb8 100644 --- a/examples/data/case_0d1d_liq/reactors.xml +++ b/examples/data/case_0d1d_liq/reactors.xml @@ -1,4 +1,4 @@ - + @@ -21,30 +21,26 @@ - - + + 1e-6 - - + + 1e-6 - - - - + + 3e-8 - - - + + 6e-8 - diff --git a/examples/data/simple_0d/reactors.xml b/examples/data/simple_0d/reactors.xml index d8008b15..b59faf57 100644 --- a/examples/data/simple_0d/reactors.xml +++ b/examples/data/simple_0d/reactors.xml @@ -1,4 +1,4 @@ - + @@ -12,6 +12,4 @@ - - diff --git a/examples/python/mixing_simple.py b/examples/python/mixing_simple.py index 17258f55..650cdf2d 100644 --- a/examples/python/mixing_simple.py +++ b/examples/python/mixing_simple.py @@ -76,10 +76,10 @@ def initial_c_distribution(n_c): """ Generate a random initial concentration distribution for a simulation with `n_c` compartments. """ - # return np.random.random((1, n_c)) - m = np.zeros((n_c,)) - m[0] = 1 - return m + return np.random.random((1, n_c)) + # m = np.zeros((n_c,)) + # m[0] = 1 + # return m def get_normalized(it, y, i): @@ -121,8 +121,12 @@ def check_mixing(fmt, final_time: float): print("Final variance: ", np.var(c_final, axis=0)) plt.figure() - plt.plot(c_final) - plt.title("Normalized concentration in all compartments") + plt.style.use("tableau-colorblind10") + plt.plot(c_final, label="final") + plt.plot(c_init, "x", markersize=2.5, label="init") + plt.title("Normalized concentration in all compartments (1 is the target value)") + plt.xlabel("Compartment ID") + plt.ylabel("Normalized concentration") plt.legend() plt.show() assert abs(m0m - mfm) < 1e-8 diff --git a/pyproject.toml b/pyproject.toml index f80e6178..cb2a54bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,6 +8,12 @@ dependencies = ["numpy>=2.3.0", "scipy>=1.15"] requires-python = ">=3.12" dynamic = ["version"] +[project.optional-dependencies] +examples = [ + "matplotlib", + "pyqt5", +] + [tool.maturin] python-source = "cmtool-python/" profile = "release" diff --git a/uv.lock b/uv.lock index baae651e..4ad2d777 100644 --- a/uv.lock +++ b/uv.lock @@ -1,7 +1,249 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12" +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "fonttools" +version = "4.61.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" }, + { url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" }, + { url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295, upload-time = "2025-12-12T17:30:10.56Z" }, + { url = "https://files.pythonhosted.org/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109, upload-time = "2025-12-12T17:30:12.874Z" }, + { url = "https://files.pythonhosted.org/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598, upload-time = "2025-12-12T17:30:15.79Z" }, + { url = "https://files.pythonhosted.org/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060, upload-time = "2025-12-12T17:30:18.058Z" }, + { url = "https://files.pythonhosted.org/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078, upload-time = "2025-12-12T17:30:22.862Z" }, + { url = "https://files.pythonhosted.org/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c", size = 2846454, upload-time = "2025-12-12T17:30:24.938Z" }, + { url = "https://files.pythonhosted.org/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e", size = 2398191, upload-time = "2025-12-12T17:30:27.343Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5", size = 4928410, upload-time = "2025-12-12T17:30:29.771Z" }, + { url = "https://files.pythonhosted.org/packages/b0/8d/6fb3494dfe61a46258cd93d979cf4725ded4eb46c2a4ca35e4490d84daea/fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd", size = 4984460, upload-time = "2025-12-12T17:30:32.073Z" }, + { url = "https://files.pythonhosted.org/packages/f7/f1/a47f1d30b3dc00d75e7af762652d4cbc3dff5c2697a0dbd5203c81afd9c3/fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3", size = 4925800, upload-time = "2025-12-12T17:30:34.339Z" }, + { url = "https://files.pythonhosted.org/packages/a7/01/e6ae64a0981076e8a66906fab01539799546181e32a37a0257b77e4aa88b/fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d", size = 5067859, upload-time = "2025-12-12T17:30:36.593Z" }, + { url = "https://files.pythonhosted.org/packages/73/aa/28e40b8d6809a9b5075350a86779163f074d2b617c15d22343fce81918db/fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c", size = 2267821, upload-time = "2025-12-12T17:30:38.478Z" }, + { url = "https://files.pythonhosted.org/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b", size = 2318169, upload-time = "2025-12-12T17:30:40.951Z" }, + { url = "https://files.pythonhosted.org/packages/32/8f/4e7bf82c0cbb738d3c2206c920ca34ca74ef9dabde779030145d28665104/fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd", size = 2846094, upload-time = "2025-12-12T17:30:43.511Z" }, + { url = "https://files.pythonhosted.org/packages/71/09/d44e45d0a4f3a651f23a1e9d42de43bc643cce2971b19e784cc67d823676/fonttools-4.61.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e", size = 2396589, upload-time = "2025-12-12T17:30:45.681Z" }, + { url = "https://files.pythonhosted.org/packages/89/18/58c64cafcf8eb677a99ef593121f719e6dcbdb7d1c594ae5a10d4997ca8a/fonttools-4.61.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c", size = 4877892, upload-time = "2025-12-12T17:30:47.709Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ec/9e6b38c7ba1e09eb51db849d5450f4c05b7e78481f662c3b79dbde6f3d04/fonttools-4.61.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75", size = 4972884, upload-time = "2025-12-12T17:30:49.656Z" }, + { url = "https://files.pythonhosted.org/packages/5e/87/b5339da8e0256734ba0dbbf5b6cdebb1dd79b01dc8c270989b7bcd465541/fonttools-4.61.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063", size = 4924405, upload-time = "2025-12-12T17:30:51.735Z" }, + { url = "https://files.pythonhosted.org/packages/0b/47/e3409f1e1e69c073a3a6fd8cb886eb18c0bae0ee13db2c8d5e7f8495e8b7/fonttools-4.61.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2", size = 5035553, upload-time = "2025-12-12T17:30:54.823Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b6/1f6600161b1073a984294c6c031e1a56ebf95b6164249eecf30012bb2e38/fonttools-4.61.1-cp314-cp314-win32.whl", hash = "sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c", size = 2271915, upload-time = "2025-12-12T17:30:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/52/7b/91e7b01e37cc8eb0e1f770d08305b3655e4f002fc160fb82b3390eabacf5/fonttools-4.61.1-cp314-cp314-win_amd64.whl", hash = "sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c", size = 2323487, upload-time = "2025-12-12T17:30:59.804Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/908ad78e46c61c3e3ed70c3b58ff82ab48437faf84ec84f109592cabbd9f/fonttools-4.61.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa", size = 2929571, upload-time = "2025-12-12T17:31:02.574Z" }, + { url = "https://files.pythonhosted.org/packages/bd/41/975804132c6dea64cdbfbaa59f3518a21c137a10cccf962805b301ac6ab2/fonttools-4.61.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91", size = 2435317, upload-time = "2025-12-12T17:31:04.974Z" }, + { url = "https://files.pythonhosted.org/packages/b0/5a/aef2a0a8daf1ebaae4cfd83f84186d4a72ee08fd6a8451289fcd03ffa8a4/fonttools-4.61.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19", size = 4882124, upload-time = "2025-12-12T17:31:07.456Z" }, + { url = "https://files.pythonhosted.org/packages/80/33/d6db3485b645b81cea538c9d1c9219d5805f0877fda18777add4671c5240/fonttools-4.61.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba", size = 5100391, upload-time = "2025-12-12T17:31:09.732Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d6/675ba631454043c75fcf76f0ca5463eac8eb0666ea1d7badae5fea001155/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7", size = 4978800, upload-time = "2025-12-12T17:31:11.681Z" }, + { url = "https://files.pythonhosted.org/packages/7f/33/d3ec753d547a8d2bdaedd390d4a814e8d5b45a093d558f025c6b990b554c/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118", size = 5006426, upload-time = "2025-12-12T17:31:13.764Z" }, + { url = "https://files.pythonhosted.org/packages/b4/40/cc11f378b561a67bea850ab50063366a0d1dd3f6d0a30ce0f874b0ad5664/fonttools-4.61.1-cp314-cp314t-win32.whl", hash = "sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5", size = 2335377, upload-time = "2025-12-12T17:31:16.49Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ff/c9a2b66b39f8628531ea58b320d66d951267c98c6a38684daa8f50fb02f8/fonttools-4.61.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b", size = 2400613, upload-time = "2025-12-12T17:31:18.769Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, + { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" }, + { url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756, upload-time = "2025-08-10T21:26:13.096Z" }, + { url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404, upload-time = "2025-08-10T21:26:14.457Z" }, + { url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410, upload-time = "2025-08-10T21:26:15.73Z" }, + { url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631, upload-time = "2025-08-10T21:26:17.045Z" }, + { url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963, upload-time = "2025-08-10T21:26:18.737Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295, upload-time = "2025-08-10T21:26:20.11Z" }, + { url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987, upload-time = "2025-08-10T21:26:21.49Z" }, + { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" }, + { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" }, + { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, + { url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681, upload-time = "2025-08-10T21:26:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464, upload-time = "2025-08-10T21:26:27.733Z" }, + { url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961, upload-time = "2025-08-10T21:26:28.729Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607, upload-time = "2025-08-10T21:26:29.798Z" }, + { url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546, upload-time = "2025-08-10T21:26:31.401Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482, upload-time = "2025-08-10T21:26:32.721Z" }, + { url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720, upload-time = "2025-08-10T21:26:34.032Z" }, + { url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907, upload-time = "2025-08-10T21:26:35.824Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334, upload-time = "2025-08-10T21:26:37.534Z" }, + { url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313, upload-time = "2025-08-10T21:26:39.191Z" }, + { url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970, upload-time = "2025-08-10T21:26:40.828Z" }, + { url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894, upload-time = "2025-08-10T21:26:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995, upload-time = "2025-08-10T21:26:43.889Z" }, + { url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510, upload-time = "2025-08-10T21:26:44.915Z" }, + { url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903, upload-time = "2025-08-10T21:26:45.934Z" }, + { url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402, upload-time = "2025-08-10T21:26:47.101Z" }, + { url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135, upload-time = "2025-08-10T21:26:48.665Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409, upload-time = "2025-08-10T21:26:50.335Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763, upload-time = "2025-08-10T21:26:51.867Z" }, + { url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643, upload-time = "2025-08-10T21:26:53.592Z" }, + { url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818, upload-time = "2025-08-10T21:26:55.051Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963, upload-time = "2025-08-10T21:26:56.421Z" }, + { url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639, upload-time = "2025-08-10T21:26:57.882Z" }, + { url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741, upload-time = "2025-08-10T21:26:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" }, + { url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806, upload-time = "2025-08-10T21:27:01.537Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605, upload-time = "2025-08-10T21:27:03.335Z" }, + { url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925, upload-time = "2025-08-10T21:27:04.339Z" }, + { url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414, upload-time = "2025-08-10T21:27:05.437Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272, upload-time = "2025-08-10T21:27:07.063Z" }, + { url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578, upload-time = "2025-08-10T21:27:08.452Z" }, + { url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607, upload-time = "2025-08-10T21:27:10.125Z" }, + { url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150, upload-time = "2025-08-10T21:27:11.484Z" }, + { url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979, upload-time = "2025-08-10T21:27:12.917Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456, upload-time = "2025-08-10T21:27:14.353Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621, upload-time = "2025-08-10T21:27:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417, upload-time = "2025-08-10T21:27:17.436Z" }, + { url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582, upload-time = "2025-08-10T21:27:18.436Z" }, + { url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514, upload-time = "2025-08-10T21:27:19.465Z" }, + { url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905, upload-time = "2025-08-10T21:27:20.51Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399, upload-time = "2025-08-10T21:27:21.496Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197, upload-time = "2025-08-10T21:27:22.604Z" }, + { url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125, upload-time = "2025-08-10T21:27:24.036Z" }, + { url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612, upload-time = "2025-08-10T21:27:25.773Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990, upload-time = "2025-08-10T21:27:27.089Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601, upload-time = "2025-08-10T21:27:29.343Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041, upload-time = "2025-08-10T21:27:30.754Z" }, + { url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897, upload-time = "2025-08-10T21:27:32.803Z" }, + { url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" }, + { url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" }, + { url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269, upload-time = "2025-12-10T22:56:51.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453, upload-time = "2025-12-10T22:55:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321, upload-time = "2025-12-10T22:55:33.265Z" }, + { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944, upload-time = "2025-12-10T22:55:34.922Z" }, + { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099, upload-time = "2025-12-10T22:55:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040, upload-time = "2025-12-10T22:55:38.715Z" }, + { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717, upload-time = "2025-12-10T22:55:41.103Z" }, + { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751, upload-time = "2025-12-10T22:55:42.684Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6", size = 8261076, upload-time = "2025-12-10T22:55:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1", size = 8148794, upload-time = "2025-12-10T22:55:46.252Z" }, + { url = "https://files.pythonhosted.org/packages/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486", size = 8718474, upload-time = "2025-12-10T22:55:47.864Z" }, + { url = "https://files.pythonhosted.org/packages/01/be/cd478f4b66f48256f42927d0acbcd63a26a893136456cd079c0cc24fbabf/matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce", size = 9549637, upload-time = "2025-12-10T22:55:50.048Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8dc289776eae5109e268c4fb92baf870678dc048a25d4ac903683b86d5bf/matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6", size = 9613678, upload-time = "2025-12-10T22:55:52.21Z" }, + { url = "https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149", size = 8142686, upload-time = "2025-12-10T22:55:54.253Z" }, + { url = "https://files.pythonhosted.org/packages/66/52/8d8a8730e968185514680c2a6625943f70269509c3dcfc0dcf7d75928cb8/matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645", size = 8012917, upload-time = "2025-12-10T22:55:56.268Z" }, + { url = "https://files.pythonhosted.org/packages/b5/27/51fe26e1062f298af5ef66343d8ef460e090a27fea73036c76c35821df04/matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077", size = 8305679, upload-time = "2025-12-10T22:55:57.856Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1e/4de865bc591ac8e3062e835f42dd7fe7a93168d519557837f0e37513f629/matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22", size = 8198336, upload-time = "2025-12-10T22:55:59.371Z" }, + { url = "https://files.pythonhosted.org/packages/c6/cb/2f7b6e75fb4dce87ef91f60cac4f6e34f4c145ab036a22318ec837971300/matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39", size = 8731653, upload-time = "2025-12-10T22:56:01.032Z" }, + { url = "https://files.pythonhosted.org/packages/46/b3/bd9c57d6ba670a37ab31fb87ec3e8691b947134b201f881665b28cc039ff/matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565", size = 9561356, upload-time = "2025-12-10T22:56:02.95Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3d/8b94a481456dfc9dfe6e39e93b5ab376e50998cddfd23f4ae3b431708f16/matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a", size = 9614000, upload-time = "2025-12-10T22:56:05.411Z" }, + { url = "https://files.pythonhosted.org/packages/bd/cd/bc06149fe5585ba800b189a6a654a75f1f127e8aab02fd2be10df7fa500c/matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958", size = 8220043, upload-time = "2025-12-10T22:56:07.551Z" }, + { url = "https://files.pythonhosted.org/packages/e3/de/b22cf255abec916562cc04eef457c13e58a1990048de0c0c3604d082355e/matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5", size = 8062075, upload-time = "2025-12-10T22:56:09.178Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/9c0ff7a2f11615e516c3b058e1e6e8f9614ddeca53faca06da267c48345d/matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f", size = 8262481, upload-time = "2025-12-10T22:56:10.885Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b", size = 8151473, upload-time = "2025-12-10T22:56:12.377Z" }, + { url = "https://files.pythonhosted.org/packages/f1/6f/009d129ae70b75e88cbe7e503a12a4c0670e08ed748a902c2568909e9eb5/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d", size = 9553896, upload-time = "2025-12-10T22:56:14.432Z" }, + { url = "https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008", size = 9824193, upload-time = "2025-12-10T22:56:16.29Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/3abf75f38605772cf48a9daf5821cd4f563472f38b4b828c6fba6fa6d06e/matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c", size = 9615444, upload-time = "2025-12-10T22:56:18.155Z" }, + { url = "https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11", size = 8272719, upload-time = "2025-12-10T22:56:20.366Z" }, + { url = "https://files.pythonhosted.org/packages/69/ce/b006495c19ccc0a137b48083168a37bd056392dee02f87dba0472f2797fe/matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8", size = 8144205, upload-time = "2025-12-10T22:56:22.239Z" }, + { url = "https://files.pythonhosted.org/packages/68/d9/b31116a3a855bd313c6fcdb7226926d59b041f26061c6c5b1be66a08c826/matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50", size = 8305785, upload-time = "2025-12-10T22:56:24.218Z" }, + { url = "https://files.pythonhosted.org/packages/1e/90/6effe8103f0272685767ba5f094f453784057072f49b393e3ea178fe70a5/matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908", size = 8198361, upload-time = "2025-12-10T22:56:26.787Z" }, + { url = "https://files.pythonhosted.org/packages/d7/65/a73188711bea603615fc0baecca1061429ac16940e2385433cc778a9d8e7/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a", size = 9561357, upload-time = "2025-12-10T22:56:28.953Z" }, + { url = "https://files.pythonhosted.org/packages/f4/3d/b5c5d5d5be8ce63292567f0e2c43dde9953d3ed86ac2de0a72e93c8f07a1/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1", size = 9823610, upload-time = "2025-12-10T22:56:31.455Z" }, + { url = "https://files.pythonhosted.org/packages/4d/4b/e7beb6bbd49f6bae727a12b270a2654d13c397576d25bd6786e47033300f/matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c", size = 9614011, upload-time = "2025-12-10T22:56:33.85Z" }, + { url = "https://files.pythonhosted.org/packages/7c/e6/76f2813d31f032e65f6f797e3f2f6e4aab95b65015924b1c51370395c28a/matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b", size = 8362801, upload-time = "2025-12-10T22:56:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560, upload-time = "2025-12-10T22:56:38.008Z" }, +] + [[package]] name = "numpy" version = "2.3.4" @@ -65,6 +307,84 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/23/08c002201a8e7e1f9afba93b97deceb813252d9cfd0d3351caed123dcf97/numpy-2.3.4-cp314-cp314t-win_arm64.whl", hash = "sha256:8b5a9a39c45d852b62693d9b3f3e0fe052541f804296ff401a72a1b60edafb29", size = 10547532, upload-time = "2025-10-15T16:17:53.48Z" }, ] +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pillow" +version = "12.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" }, + { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" }, + { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" }, + { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" }, + { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" }, + { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" }, + { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" }, + { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" }, + { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" }, + { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" }, + { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" }, + { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" }, + { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" }, + { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" }, + { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" }, + { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" }, + { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" }, + { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" }, + { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" }, + { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" }, + { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" }, + { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" }, + { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" }, + { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" }, + { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" }, + { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" }, + { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" }, + { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" }, + { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" }, + { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" }, + { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" }, + { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" }, + { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" }, + { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" }, + { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" }, + { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" }, + { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" }, + { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" }, + { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" }, + { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" }, +] + [[package]] name = "pycmtool" source = { editable = "." } @@ -73,11 +393,91 @@ dependencies = [ { name = "scipy" }, ] +[package.optional-dependencies] +examples = [ + { name = "matplotlib" }, + { name = "pyqt5" }, +] + [package.metadata] requires-dist = [ + { name = "matplotlib", marker = "extra == 'examples'" }, { name = "numpy", specifier = ">=2.3.0" }, + { name = "pyqt5", marker = "extra == 'examples'" }, { name = "scipy", specifier = ">=1.15" }, ] +provides-extras = ["examples"] + +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + +[[package]] +name = "pyqt5" +version = "5.15.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyqt5-qt5" }, + { name = "pyqt5-sip" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/07/c9ed0bd428df6f87183fca565a79fee19fa7c88c7f00a7f011ab4379e77a/PyQt5-5.15.11.tar.gz", hash = "sha256:fda45743ebb4a27b4b1a51c6d8ef455c4c1b5d610c90d2934c7802b5c1557c52", size = 3216775, upload-time = "2024-07-19T08:39:57.756Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/64/42ec1b0bd72d87f87bde6ceb6869f444d91a2d601f2e67cd05febc0346a1/PyQt5-5.15.11-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c8b03dd9380bb13c804f0bdb0f4956067f281785b5e12303d529f0462f9afdc2", size = 6579776, upload-time = "2024-07-19T08:39:19.775Z" }, + { url = "https://files.pythonhosted.org/packages/49/f5/3fb696f4683ea45d68b7e77302eff173493ac81e43d63adb60fa760b9f91/PyQt5-5.15.11-cp38-abi3-macosx_11_0_x86_64.whl", hash = "sha256:6cd75628f6e732b1ffcfe709ab833a0716c0445d7aec8046a48d5843352becb6", size = 7016415, upload-time = "2024-07-19T08:39:32.977Z" }, + { url = "https://files.pythonhosted.org/packages/b4/8c/4065950f9d013c4b2e588fe33cf04e564c2322842d84dbcbce5ba1dc28b0/PyQt5-5.15.11-cp38-abi3-manylinux_2_17_x86_64.whl", hash = "sha256:cd672a6738d1ae33ef7d9efa8e6cb0a1525ecf53ec86da80a9e1b6ec38c8d0f1", size = 8188103, upload-time = "2024-07-19T08:39:40.561Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/ae5a5b4f9b826b29ea4be841b2f2d951bcf5ae1d802f3732b145b57c5355/PyQt5-5.15.11-cp38-abi3-win32.whl", hash = "sha256:76be0322ceda5deecd1708a8d628e698089a1cea80d1a49d242a6d579a40babd", size = 5433308, upload-time = "2024-07-19T08:39:46.932Z" }, + { url = "https://files.pythonhosted.org/packages/56/d5/68eb9f3d19ce65df01b6c7b7a577ad3bbc9ab3a5dd3491a4756e71838ec9/PyQt5-5.15.11-cp38-abi3-win_amd64.whl", hash = "sha256:bdde598a3bb95022131a5c9ea62e0a96bd6fb28932cc1619fd7ba211531b7517", size = 6865864, upload-time = "2024-07-19T08:39:53.572Z" }, +] + +[[package]] +name = "pyqt5-qt5" +version = "5.15.18" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/90/bf01ac2132400997a3474051dd680a583381ebf98b2f5d64d4e54138dc42/pyqt5_qt5-5.15.18-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:8bb997eb903afa9da3221a0c9e6eaa00413bbeb4394d5706118ad05375684767", size = 39715743, upload-time = "2025-11-09T12:56:42.936Z" }, + { url = "https://files.pythonhosted.org/packages/24/8e/76366484d9f9dbe28e3bdfc688183433a7b82e314216e9b14c89e5fab690/pyqt5_qt5-5.15.18-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c656af9c1e6aaa7f59bf3d8995f2fa09adbf6762b470ed284c31dca80d686a26", size = 36798484, upload-time = "2025-11-09T12:56:59.998Z" }, + { url = "https://files.pythonhosted.org/packages/9a/46/ffe177f99f897a59dc237a20059020427bd2d3853d713992b8081933ddfe/pyqt5_qt5-5.15.18-py3-none-manylinux2014_x86_64.whl", hash = "sha256:bf2457e6371969736b4f660a0c153258fa03dbc6a181348218e6f05421682af7", size = 60864590, upload-time = "2025-11-09T12:57:26.724Z" }, +] + +[[package]] +name = "pyqt5-sip" +version = "12.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/31/5ef342de9faee0f3801088946ae103db9b9eaeba3d6a64fefd5ce74df244/pyqt5_sip-12.18.0.tar.gz", hash = "sha256:71c37db75a0664325de149f43e2a712ec5fa1f90429a21dafbca005cb6767f94", size = 104143, upload-time = "2026-01-13T15:53:19.576Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/61/6d78d702016ac23d2b97634a3b6a831c3f7735f0552a1c8b058db96005d1/pyqt5_sip-12.18.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b29e4cda24748e59e5bd1bdad4812091a86b4b5b08c38b7f781eb55a5166f2b7", size = 124614, upload-time = "2026-01-13T15:52:57.59Z" }, + { url = "https://files.pythonhosted.org/packages/19/bf/8f3efa10ddd3e76c1253865340ab7c2960ef96681d732b1f666c77430612/pyqt5_sip-12.18.0-cp312-cp312-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:163c2bba5e637c2222ec17d82a2c5aa158184a191923eb7d137cf4cfa0399529", size = 339412, upload-time = "2026-01-13T15:53:00.563Z" }, + { url = "https://files.pythonhosted.org/packages/72/48/f1bcf6729d01bae6729cd790b22fd579dbe34014e8be031e6f10c5b9b2aa/pyqt5_sip-12.18.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ead5e0a64ad852ac60797989d8444a6a5bd834768536b04a07b40b2937d922f6", size = 282376, upload-time = "2026-01-13T15:52:59.172Z" }, + { url = "https://files.pythonhosted.org/packages/dd/b7/d84c764ac9f1366be561255ec9bd88ee224fefdbdb349aee250f3003f0ca/pyqt5_sip-12.18.0-cp312-cp312-win32.whl", hash = "sha256:993fe3ed9a62a92e770f32d5344e3df56c2cacf1471f01b7feaf04818a2df1c4", size = 49523, upload-time = "2026-01-13T15:53:03.068Z" }, + { url = "https://files.pythonhosted.org/packages/ab/e7/ef87178d5afa5f63be38556dc0df8af89f9bf74f2555f4dab6824c0fd150/pyqt5_sip-12.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:9b689e02e400abd1ce0a30cd6eae8eceabcf1bbba0395cb5c86e64ba74351d68", size = 58001, upload-time = "2026-01-13T15:53:02.15Z" }, + { url = "https://files.pythonhosted.org/packages/79/67/8d43d0fea10ff48ddecc8534aead8b855dc80df80653b8b1bf9e1f993063/pyqt5_sip-12.18.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9254e5dd7676b76503ba20edcc919e7ac4a97b6c70a6fb2f9dba9e13b4c60509", size = 124605, upload-time = "2026-01-13T15:53:04.991Z" }, + { url = "https://files.pythonhosted.org/packages/48/2a/b08bc8efeb49c50c6cdac11417dc2c8eaefcac2f0a6382eae7b26dc0f232/pyqt5_sip-12.18.0-cp313-cp313-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c969631ada7293a81e1012b2264a62d69a91995b517586489dfe24421b87b9af", size = 339918, upload-time = "2026-01-13T15:53:08.502Z" }, + { url = "https://files.pythonhosted.org/packages/b6/99/24f82437b2f073cf39296b7c731b6a8bc0f5207911fdd93841a0ea9abe42/pyqt5_sip-12.18.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d84ac384a63285132e67762c87681191c25e28a1df7560287ec3889d9eb223b5", size = 282088, upload-time = "2026-01-13T15:53:06.632Z" }, + { url = "https://files.pythonhosted.org/packages/3e/27/20d3924943df34361fae9c6a0489ae89d0b07571693245c61678d185e4a4/pyqt5_sip-12.18.0-cp313-cp313-win32.whl", hash = "sha256:95bba4670ecf5cba73958b85aa2087c17838a402ed251c38e68060c7665c998b", size = 49501, upload-time = "2026-01-13T15:53:11.159Z" }, + { url = "https://files.pythonhosted.org/packages/3f/36/e251623c12968730730512a9e5150430e36246afbe64894610190b896f61/pyqt5_sip-12.18.0-cp313-cp313-win_amd64.whl", hash = "sha256:aac4adc37df2f2ac1dc259409be1900f07332d140a12c9db7c84112cef64ff59", size = 58076, upload-time = "2026-01-13T15:53:09.928Z" }, + { url = "https://files.pythonhosted.org/packages/37/3a/b46a0116b1aacbb6156b2957eb5cb928c94b49f4626eb2540ca8d16ee757/pyqt5_sip-12.18.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8372ec8704bfd5e09942d0d055a1657eb4f702f4b30847a5e59df0496f99d67f", size = 124594, upload-time = "2026-01-13T15:53:13.159Z" }, + { url = "https://files.pythonhosted.org/packages/58/63/df3037f11391c25c5b0ab233d22e58b8f056cb1ce16d7ecadb844421ce75/pyqt5_sip-12.18.0-cp314-cp314-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fdb45c7cd2af7eccd7370b994d432bfc7965079f845392760724f26771bb59dc", size = 339056, upload-time = "2026-01-13T15:53:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e7/4f96b84520b8f8b7502682fd43f68f63ca6572b5858f56e5f61c76a54fe2/pyqt5_sip-12.18.0-cp314-cp314-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:92abe984becbde768954d6d0951f56d80a9868d2fd9e738e61fc944f0ff83dd6", size = 282439, upload-time = "2026-01-13T15:53:14.856Z" }, + { url = "https://files.pythonhosted.org/packages/79/8e/ccdf20d373ceba83e1d1b7f818505c375208ffde4a96376dc7dbe592406c/pyqt5_sip-12.18.0-cp314-cp314-win32.whl", hash = "sha256:bd9e3c6f81346f1b08d6db02305cdee20c009b43aa083d44ee2de47a7da0e123", size = 50713, upload-time = "2026-01-13T15:53:18.634Z" }, + { url = "https://files.pythonhosted.org/packages/7f/21/8486ed45977be615ec5371b24b47298b1cb0e1a455b419eddd0215078dba/pyqt5_sip-12.18.0-cp314-cp314-win_amd64.whl", hash = "sha256:6d948f1be619c645cd3bda54952bfdc1aef7c79242dccea6a6858748e61114b9", size = 59622, upload-time = "2026-01-13T15:53:17.714Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] [[package]] name = "scipy" @@ -139,3 +539,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/61/82/8d0e39f62764cce5ffd5284131e109f07cf8955aef9ab8ed4e3aa5e30539/scipy-1.16.3-cp314-cp314t-win_amd64.whl", hash = "sha256:d9f48cafc7ce94cf9b15c6bffdc443a81a27bf7075cf2dcd5c8b40f85d10c4e7", size = 39471128, upload-time = "2025-10-28T17:38:05.259Z" }, { url = "https://files.pythonhosted.org/packages/64/47/a494741db7280eae6dc033510c319e34d42dd41b7ac0c7ead39354d1a2b5/scipy-1.16.3-cp314-cp314t-win_arm64.whl", hash = "sha256:21d9d6b197227a12dcbf9633320a4e34c6b0e51c57268df255a0942983bac562", size = 26464127, upload-time = "2025-10-28T17:38:11.34Z" }, ] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] From b20e2bb0d3cb04d38b8bd62839fde95fcc2fe124 Mon Sep 17 00:00:00 2001 From: bcasale Date: Thu, 19 Feb 2026 16:08:27 +0100 Subject: [PATCH 24/44] fix(assemble): panic if feed/connection id is not known --- cmtool-assemble/src/parser/reactors.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmtool-assemble/src/parser/reactors.rs b/cmtool-assemble/src/parser/reactors.rs index 2633e721..52620b28 100644 --- a/cmtool-assemble/src/parser/reactors.rs +++ b/cmtool-assemble/src/parser/reactors.rs @@ -51,7 +51,11 @@ fn connection_per_phase( } } } else { - eprintln!("Ignored connection"); + eprintln!( + "Ignored connection src:{} {}", + node.source.id, node.target.id + ); + panic!("TODO: Handle error when flux doesnt work") } } rd From d85fdac9b1264ed0c756dd8899923848728bbe0f Mon Sep 17 00:00:00 2001 From: bcasale Date: Fri, 20 Feb 2026 18:48:46 +0100 Subject: [PATCH 25/44] feat(pycmtool): expose neighbors getter for flowmaps --- cmtool-data/src/flowmap.rs | 5 ++++- cmtool-python/src/rd.rs | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/cmtool-data/src/flowmap.rs b/cmtool-data/src/flowmap.rs index 5e5d81c7..b488306e 100644 --- a/cmtool-data/src/flowmap.rs +++ b/cmtool-data/src/flowmap.rs @@ -4,7 +4,7 @@ use crate::{DataError, RawData, RawFlux, rawdata}; pub struct FlowMapDescriptor { pub flowmap: Array2, - pub(crate) neighbors: Array2, //TODO + pub neighbors: Array2, //TODO pub volumes: Vec, } @@ -60,6 +60,9 @@ impl FlowMapDescriptor { .ok_or(DataError::BadData)?; let mut neighbor_flat = Array2::::zeros((n_zone, max_size)); + + neighbor_flat.fill(n_zone + 1); //Any ghost neighbor will have value n+1 + for (i_zone, neighbors_for_zone) in neighbors.iter().enumerate() { for (i_n, id_neighbor) in neighbors_for_zone.iter().enumerate() { *(neighbor_flat.get_mut((i_zone, i_n)).unwrap()) = *id_neighbor; diff --git a/cmtool-python/src/rd.rs b/cmtool-python/src/rd.rs index 9213d125..ae9998ca 100644 --- a/cmtool-python/src/rd.rs +++ b/cmtool-python/src/rd.rs @@ -110,6 +110,29 @@ impl FlowMapDescriptorWrapper { unsafe { PyArray2::borrow_from_array(flowmap, this.into_any()) } } + #[getter] + pub fn neighbors(this: Bound<'_, Self>) -> Bound<'_, PyArray2> { + let flowmap = &this.borrow().0.neighbors; + // SAFETY: + // - The returned NumPy array shares memory with the internal `flowmap` (Array2). + // - We use `borrow_from_array`, which ties the array's lifetime to the Python object (`this`). + // - This guarantees that the underlying Rust memory remains valid as long as Python holds the array. + // + // Critical Requirements: + // - `self.0.flowmap` must not be mutated in a way that causes memory reallocation (e.g., replacing it). + // For example, the following code is unsafe if it runs after `pyobject.flowmap` is accessed: + // + // fn drop(&mut self) { + // self.0.flowmap = Array2::zeros((1, 1)); // BAD: reallocates backing buffer + // } + // + // - Violating this invariant (e.g., replacing the array or shrinking it) while Python holds a reference + // will cause undefined behavior (likely a segmentation fault). + // + // - Only expose immutable views or ensure exclusive access if mutations are needed. + unsafe { PyArray2::borrow_from_array(flowmap, this.into_any()) } + } + #[getter] pub fn volumes(&self, py: Python<'_>) -> Py> { let array = numpy::ndarray::Array1::from(self.0.volumes.clone()); From dcd235f9f1f2c38ec01607074a409555ecbebdd2 Mon Sep 17 00:00:00 2001 From: bcasale Date: Fri, 20 Feb 2026 18:49:40 +0100 Subject: [PATCH 26/44] feat(core): method to extract mesh boundary as compartment id --- cmtool-core/src/grid/mod.rs | 85 ++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/cmtool-core/src/grid/mod.rs b/cmtool-core/src/grid/mod.rs index 08d381dd..f6c8570f 100644 --- a/cmtool-core/src/grid/mod.rs +++ b/cmtool-core/src/grid/mod.rs @@ -287,6 +287,9 @@ pub trait CompartmentMeshManip { fn n_maximum_interface(&self) -> usize; fn get_interface_plane(&self, cell1_id: usize, cell2_id: usize) -> (BoundedPlane, usize); + + fn cell_from_ax_points(&self, axis_points: &AxisPoints) -> Option; + fn get_boundary(&self) -> Vec; } /// A compartment mesh grid. /// @@ -562,6 +565,23 @@ impl CompartmentMeshManip for MeshCylindrical { delta_ijk[height_axis] * surface_ij } + fn cell_from_ax_points(&self, axis_points: &AxisPoints) -> Option { + let mut cell_1d = 0; + let mut multiplier = 1; + + for i in (0..self.axes.len()).rev() { + let n = self.axes[i].descriptor.n_range; + if axis_points[i] >= n { + return None; + } + cell_1d += axis_points[i] * multiplier; + + multiplier *= n; + } + + Some(cell_1d) + } + fn cell_from_coordinates(&self, coords: &Coords3) -> Option { let mut mesh_id = 0; let mut cumulative_product = 1; @@ -589,7 +609,6 @@ impl CompartmentMeshManip for MeshCylindrical { fn cell_points(&self, cell_1d: usize) -> AxisPoints { let mut axis_points = AxisPoints::default(); - let mut p_coeff_up = cell_1d; // for (current_point, current_axis) in axis_points.iter_mut().zip(&self.axes) { @@ -608,6 +627,45 @@ impl CompartmentMeshManip for MeshCylindrical { axis_points } + + fn get_boundary(&self) -> Vec { + let (n_r, n_theta, n_z) = ( + self.n_points_axis(0), + self.n_points_axis(1), + self.n_points_axis(2), + ); + + let expected = n_theta * (n_z - 2) + 2 * n_r * n_z; + let mut v = Vec::with_capacity(expected); + + for i in 0..n_r { + for j in 0..n_theta { + let p = self + .cell_from_ax_points(&[i, j, 0]) + .expect("get_boundary: out of bound "); + let p2 = self + .cell_from_ax_points(&[i, j, n_z - 1]) + .expect("get_boundary: out of bound "); + v.push(p); + v.push(p2); + } + } + + for k in 1..n_z - 1 { + for j in 0..n_theta { + let p = self + .cell_from_ax_points(&[n_r - 1, j, k]) + .expect("get_boundary: out of bound "); + v.push(p) + } + } + + if expected != v.len() { + panic!("Detected number is not correct {} {}", expected, v.len()); + } + + v + } } pub fn get_mesh( @@ -790,6 +848,31 @@ mod test { ); } + #[test] + fn t_boundary_cylindrical() { + let ax1 = AxisDescriptor::new(0., max_ax1, 5); + let ax2 = AxisDescriptor::new(-std::f64::consts::PI, std::f64::consts::PI, 10); + let ax3 = AxisDescriptor::new(0., max_ax3, 10); + let mesh = get_mesh(MeshType::Cylindrical, [ax1, ax2, ax3]); + + let mut v = mesh.get_boundary(); + let w = [ + 0, 9, 10, 19, 20, 29, 30, 39, 40, 49, 50, 59, 60, 69, 70, 79, 80, 89, 90, 99, 100, 109, + 110, 119, 120, 129, 130, 139, 140, 149, 150, 159, 160, 169, 170, 179, 180, 189, 190, + 199, 200, 209, 210, 219, 220, 229, 230, 239, 240, 249, 250, 259, 260, 269, 270, 279, + 280, 289, 290, 299, 300, 309, 310, 319, 320, 329, 330, 339, 340, 349, 350, 359, 360, + 369, 370, 379, 380, 389, 390, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, + 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, + 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, + 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, + 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, + 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, + 495, 496, 497, 498, 499, + ]; + v.sort(); + assert_eq!(v, w); + } + #[test] fn t_neighbors_cylindrical() { use NeighborDirection::*; From a29d5d2fde44d07774b2bf29fb56c33d5e092beb Mon Sep 17 00:00:00 2001 From: bcasale Date: Fri, 20 Feb 2026 18:50:13 +0100 Subject: [PATCH 27/44] chore: add roadmap --- cmtool-assemble/datamodel/connections.xsd | 44 ++++++++++++++++--- .../src/parser/generated_domain.rs | 2 +- cmtool-core/src/model/mod.rs | 3 ++ documentations/roadmap.md | 17 +++++++ 4 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 documentations/roadmap.md diff --git a/cmtool-assemble/datamodel/connections.xsd b/cmtool-assemble/datamodel/connections.xsd index da0836dd..906230d4 100644 --- a/cmtool-assemble/datamodel/connections.xsd +++ b/cmtool-assemble/datamodel/connections.xsd @@ -1,4 +1,4 @@ - @@ -9,7 +9,8 @@ - + + @@ -26,21 +27,54 @@ - + + - + + + + + + + + + + + + + + - + + diff --git a/cmtool-assemble/src/parser/generated_domain.rs b/cmtool-assemble/src/parser/generated_domain.rs index 7ca11328..9205874e 100644 --- a/cmtool-assemble/src/parser/generated_domain.rs +++ b/cmtool-assemble/src/parser/generated_domain.rs @@ -5,4 +5,4 @@ #![allow(unused_imports)] -use serde :: { Deserialize , Serialize } ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct BaseReactorType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct ConnectionsType { # [serde (default , rename = "Flux")] pub flux : :: std :: vec :: Vec < FluxType > , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct DimensionType { # [serde (default , rename = "@unit")] pub unit : :: core :: option :: Option < :: std :: string :: String > , # [serde (rename = "#text")] pub content : :: core :: primitive :: f32 , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct FeedFluxType { # [serde (rename = "@phase")] pub phase : :: std :: string :: String , # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "Source")] pub source : NodeType , # [serde (rename = "Target")] pub target : NodeType , # [serde (rename = "Value")] pub value : DimensionType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct FeedsType { # [serde (default , rename = "Flux")] pub flux : :: std :: vec :: Vec < FeedFluxType > , } pub type FlowType = DimensionType ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct FluxType { # [serde (rename = "@phase")] pub phase : :: std :: string :: String , # [serde (rename = "Source")] pub source : NodeType , # [serde (rename = "Target")] pub target : NodeType , # [serde (rename = "Value")] pub value : DimensionType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub enum GeneralSizeType { # [serde (rename = "Volume")] Volume (DimensionType) , # [serde (rename = "Dimension")] Dimension (SizeCylindricalType) , } pub type LengthType = DimensionType ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct NodeType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "@compartment_id")] pub compartment_id : :: core :: primitive :: usize , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct Reactor0DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct Reactor1DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , # [serde (rename = "Compartments")] pub compartments : :: core :: primitive :: usize , # [serde (rename = "Dispersion")] pub dispersion : DimensionType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct Reactor3DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , # [serde (rename = "file")] pub file : :: std :: string :: String , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct ReactorFromFileType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "Path")] pub path : :: std :: string :: String , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct ReactorsType { # [serde (rename = "#content")] pub content : :: std :: vec :: Vec < ReactorsTypeContent > , } # [derive (Clone , Debug , Deserialize , Serialize)] pub enum ReactorsTypeContent { # [serde (rename = "Reactor0D")] Reactor0D (Reactor0DType) , # [serde (rename = "Reactor1D")] Reactor1D (Reactor1DType) , # [serde (rename = "Reactor3D")] Reactor3D (Reactor3DType) , # [serde (rename = "ReactorFromFile")] ReactorFromFile (ReactorFromFileType) , } pub type Root = RootElementType ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct RootElementType { # [serde (rename = "@run_id")] pub run_id : :: std :: string :: String , # [serde (rename = "@version")] pub version : :: core :: primitive :: i32 , # [serde (rename = "Reactors")] pub reactors : ReactorsType , # [serde (default , rename = "Connections")] pub connections : :: core :: option :: Option < ConnectionsType > , # [serde (default , rename = "Feeds")] pub feeds : :: core :: option :: Option < FeedsType > , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct SizeCylindricalType { # [serde (rename = "Diameter")] pub diameter : DimensionType , # [serde (rename = "Length")] pub length : DimensionType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub enum SizeSpecificationType { # [serde (rename = "Volume")] Volume (DimensionType) , # [serde (rename = "CylindricalDimensions")] CylindricalDimensions (SizeSpecificationCylindricalDimensionsElementType) , } pub type TimeType = DimensionType ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct VolumeFractionType { # [serde (default , rename = "@phase")] pub phase : :: core :: option :: Option < :: std :: string :: String > , # [serde (rename = "#text")] pub content : :: core :: primitive :: f32 , } pub type VolumeType = DimensionType ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct SizeSpecificationCylindricalDimensionsElementType { # [serde (rename = "Diameter")] pub diameter : DimensionType , # [serde (rename = "Length")] pub length : DimensionType , } pub mod xs { use serde :: { Deserialize , Serialize } ; # [derive (Clone , Debug , Default , Deserialize , Serialize)] pub struct EntitiesType (pub :: std :: vec :: Vec < :: std :: string :: String >) ; pub type EntityType = EntitiesType ; pub type IdType = :: std :: string :: String ; pub type IdrefType = :: std :: string :: String ; pub type IdrefsType = EntitiesType ; pub type NcNameType = :: std :: string :: String ; pub type NmtokenType = :: std :: string :: String ; pub type NmtokensType = EntitiesType ; pub type NotationType = :: std :: string :: String ; pub type NameType = :: std :: string :: String ; pub type QNameType = :: std :: string :: String ; pub type AnySimpleType = :: std :: string :: String ; pub type AnyUriType = :: std :: string :: String ; pub type Base64BinaryType = :: std :: string :: String ; pub type BooleanType = :: core :: primitive :: bool ; pub type ByteType = :: core :: primitive :: i8 ; pub type DateType = :: std :: string :: String ; pub type DateTimeType = :: std :: string :: String ; pub type DecimalType = :: core :: primitive :: f64 ; pub type DoubleType = :: core :: primitive :: f64 ; pub type DurationType = :: std :: string :: String ; pub type FloatType = :: core :: primitive :: f32 ; pub type GDayType = :: std :: string :: String ; pub type GMonthType = :: std :: string :: String ; pub type GMonthDayType = :: std :: string :: String ; pub type GYearType = :: std :: string :: String ; pub type GYearMonthType = :: std :: string :: String ; pub type HexBinaryType = :: std :: string :: String ; pub type IntType = :: core :: primitive :: i32 ; pub type IntegerType = :: core :: primitive :: i32 ; pub type LanguageType = :: std :: string :: String ; pub type LongType = :: core :: primitive :: i64 ; pub type NegativeIntegerType = :: core :: primitive :: isize ; pub type NonNegativeIntegerType = :: core :: primitive :: usize ; pub type NonPositiveIntegerType = :: core :: primitive :: isize ; pub type NormalizedStringType = :: std :: string :: String ; pub type PositiveIntegerType = :: core :: primitive :: usize ; pub type ShortType = :: core :: primitive :: i16 ; pub type StringType = :: std :: string :: String ; pub type TimeType = :: std :: string :: String ; pub type TokenType = :: std :: string :: String ; pub type UnsignedByteType = :: core :: primitive :: u8 ; pub type UnsignedIntType = :: core :: primitive :: u32 ; pub type UnsignedLongType = :: core :: primitive :: u64 ; pub type UnsignedShortType = :: core :: primitive :: u16 ; } \ No newline at end of file +use serde :: { Deserialize , Serialize } ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct AutoFeedType { # [serde (rename = "@phase")] pub phase : :: std :: string :: String , # [serde (rename = "D")] pub d : NodeType , # [serde (rename = "N")] pub n : NodeType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct BaseReactorType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct ConnectionsType { # [serde (default , rename = "Flux")] pub flux : :: std :: vec :: Vec < FluxType > , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct DimensionType { # [serde (default , rename = "@unit")] pub unit : :: core :: option :: Option < :: std :: string :: String > , # [serde (rename = "#text")] pub content : :: core :: primitive :: f32 , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct FeedFluxType { # [serde (rename = "@phase")] pub phase : :: std :: string :: String , # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "Source")] pub source : NodeType , # [serde (rename = "Target")] pub target : NodeType , # [serde (rename = "Value")] pub value : DimensionType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct FeedsType { # [serde (default , rename = "Flux")] pub flux : :: std :: vec :: Vec < FeedFluxType > , } pub type FlowType = DimensionType ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct FluxType { # [serde (rename = "@phase")] pub phase : :: std :: string :: String , # [serde (rename = "Source")] pub source : NodeType , # [serde (rename = "Target")] pub target : NodeType , # [serde (rename = "Value")] pub value : DimensionType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub enum GeneralSizeType { # [serde (rename = "Volume")] Volume (DimensionType) , # [serde (rename = "Dimension")] Dimension (SizeCylindricalType) , } pub type LengthType = DimensionType ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct NodeType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "@compartment_id")] pub compartment_id : :: core :: primitive :: usize , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct Reactor0DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct Reactor1DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , # [serde (rename = "Compartments")] pub compartments : :: core :: primitive :: usize , # [serde (rename = "Dispersion")] pub dispersion : DimensionType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct Reactor3DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , # [serde (rename = "file")] pub file : :: std :: string :: String , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct ReactorFromFileType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "Path")] pub path : :: std :: string :: String , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct ReactorsType { # [serde (rename = "#content")] pub content : :: std :: vec :: Vec < ReactorsTypeContent > , } # [derive (Clone , Debug , Deserialize , Serialize)] pub enum ReactorsTypeContent { # [serde (rename = "Reactor0D")] Reactor0D (Reactor0DType) , # [serde (rename = "Reactor1D")] Reactor1D (Reactor1DType) , # [serde (rename = "Reactor3D")] Reactor3D (Reactor3DType) , # [serde (rename = "ReactorFromFile")] ReactorFromFile (ReactorFromFileType) , } pub type Root = RootElementType ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct RootElementType { # [serde (rename = "@run_id")] pub run_id : :: std :: string :: String , # [serde (rename = "@version")] pub version : :: core :: primitive :: i32 , # [serde (rename = "Reactors")] pub reactors : ReactorsType , # [serde (default , rename = "Connections")] pub connections : :: core :: option :: Option < ConnectionsType > , # [serde (default , rename = "Feeds")] pub feeds : :: core :: option :: Option < FeedsType > , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct SizeCylindricalType { # [serde (rename = "Diameter")] pub diameter : DimensionType , # [serde (rename = "Length")] pub length : DimensionType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub enum SizeSpecificationType { # [serde (rename = "Volume")] Volume (DimensionType) , # [serde (rename = "CylindricalDimensions")] CylindricalDimensions (SizeSpecificationCylindricalDimensionsElementType) , } pub type TimeType = DimensionType ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct VolumeFractionType { # [serde (default , rename = "@phase")] pub phase : :: core :: option :: Option < :: std :: string :: String > , # [serde (rename = "#text")] pub content : :: core :: primitive :: f32 , } pub type VolumeType = DimensionType ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct SizeSpecificationCylindricalDimensionsElementType { # [serde (rename = "Diameter")] pub diameter : DimensionType , # [serde (rename = "Length")] pub length : DimensionType , } pub mod xs { use serde :: { Deserialize , Serialize } ; # [derive (Clone , Debug , Default , Deserialize , Serialize)] pub struct EntitiesType (pub :: std :: vec :: Vec < :: std :: string :: String >) ; pub type EntityType = EntitiesType ; pub type IdType = :: std :: string :: String ; pub type IdrefType = :: std :: string :: String ; pub type IdrefsType = EntitiesType ; pub type NcNameType = :: std :: string :: String ; pub type NmtokenType = :: std :: string :: String ; pub type NmtokensType = EntitiesType ; pub type NotationType = :: std :: string :: String ; pub type NameType = :: std :: string :: String ; pub type QNameType = :: std :: string :: String ; pub type AnySimpleType = :: std :: string :: String ; pub type AnyUriType = :: std :: string :: String ; pub type Base64BinaryType = :: std :: string :: String ; pub type BooleanType = :: core :: primitive :: bool ; pub type ByteType = :: core :: primitive :: i8 ; pub type DateType = :: std :: string :: String ; pub type DateTimeType = :: std :: string :: String ; pub type DecimalType = :: core :: primitive :: f64 ; pub type DoubleType = :: core :: primitive :: f64 ; pub type DurationType = :: std :: string :: String ; pub type FloatType = :: core :: primitive :: f32 ; pub type GDayType = :: std :: string :: String ; pub type GMonthType = :: std :: string :: String ; pub type GMonthDayType = :: std :: string :: String ; pub type GYearType = :: std :: string :: String ; pub type GYearMonthType = :: std :: string :: String ; pub type HexBinaryType = :: std :: string :: String ; pub type IntType = :: core :: primitive :: i32 ; pub type IntegerType = :: core :: primitive :: i32 ; pub type LanguageType = :: std :: string :: String ; pub type LongType = :: core :: primitive :: i64 ; pub type NegativeIntegerType = :: core :: primitive :: isize ; pub type NonNegativeIntegerType = :: core :: primitive :: usize ; pub type NonPositiveIntegerType = :: core :: primitive :: isize ; pub type NormalizedStringType = :: std :: string :: String ; pub type PositiveIntegerType = :: core :: primitive :: usize ; pub type ShortType = :: core :: primitive :: i16 ; pub type StringType = :: std :: string :: String ; pub type TimeType = :: std :: string :: String ; pub type TokenType = :: std :: string :: String ; pub type UnsignedByteType = :: core :: primitive :: u8 ; pub type UnsignedIntType = :: core :: primitive :: u32 ; pub type UnsignedLongType = :: core :: primitive :: u64 ; pub type UnsignedShortType = :: core :: primitive :: u16 ; } \ No newline at end of file diff --git a/cmtool-core/src/model/mod.rs b/cmtool-core/src/model/mod.rs index 7853dc2f..f390d38c 100644 --- a/cmtool-core/src/model/mod.rs +++ b/cmtool-core/src/model/mod.rs @@ -1,5 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-or-later +//We need to expose some grid interface to cfd-assemble +//TODO: refractor model to put cfd oriented into separated mod and move "reactor model" such as 0d/pfr from assemble to here + use crate::{ CoreError, coordinates::{CartesianCoordinates, CartesianVec3, CylindricalCoordinates}, diff --git a/documentations/roadmap.md b/documentations/roadmap.md new file mode 100644 index 00000000..06ba1100 --- /dev/null +++ b/documentations/roadmap.md @@ -0,0 +1,17 @@ +# RCMTool Roadmap + +## Chore +- refractor cmtool-core/model to put cfd oriented into separated mod and move "reactor model" such as 0d/pfr from cmtool-assemble to cmtool-core +- + +## Core + +- CFD-to_CMA algo doesn't work because of bad interface area calculation + - Scalar field ok + - vector field direction seems to be good but magnitude false because of area + +## Assemble + +- Add autofeed tag in xml datamodel + - Auto calculate flowrate from given dilution rate + - If cfd-based reactor select different source (N) to spread flowrate (Q/N) From cb5016643d8a65a091a95ebbf5623914ed6b320e Mon Sep 17 00:00:00 2001 From: bcasale Date: Mon, 23 Feb 2026 08:57:26 +0100 Subject: [PATCH 28/44] feat: compute total volume from state --- cmtool-data/src/states.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmtool-data/src/states.rs b/cmtool-data/src/states.rs index a9b99784..86be974a 100644 --- a/cmtool-data/src/states.rs +++ b/cmtool-data/src/states.rs @@ -96,6 +96,11 @@ impl HydroState { pub fn n_compartments(&self) -> usize { self.volumes.len() } + + #[inline(always)] + pub fn total_volume(&self) -> f64 { + self.volumes.iter().sum() + } } impl From for HydroState { From 5c1c95e37ea4afc46e9701c8764397085ee48b9c Mon Sep 17 00:00:00 2001 From: bcasale Date: Mon, 23 Feb 2026 10:17:01 +0100 Subject: [PATCH 29/44] chore: add comments --- CHANGELOG.md | 4 +- cmtool-assemble/src/data.rs | 13 ++++++ cmtool-assemble/src/generators.rs | 22 +++++----- cmtool-assemble/src/lib.rs | 18 ++++++++- cmtool-assemble/src/map_generation.rs | 25 +++++++----- cmtool-assemble/src/parser/mod.rs | 6 --- cmtool-assemble/src/parser/pfr_mb.rs | 10 ++--- cmtool-assemble/src/parser/reactors.rs | 56 ++++++++------------------ cmtool-cxx/src/lib.rs | 8 +++- cmtool-data/src/descriptors.rs | 6 +++ cmtool-data/src/lib.rs | 3 ++ cmtool-data/src/rawdata.rs | 28 +++++++------ cmtool-data/src/transitioner/mod.rs | 4 ++ documentations/index.html | 2 +- 14 files changed, 118 insertions(+), 87 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e2c3c98..4f77ae97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,13 @@ #### General Enhancements - Improve transitioner api and performance -- Improve documentation +- Improve documentation and examples - Improve python bindings #### Build System Changes +- Update and clean dependencies + #### Breaking Changes #### Deprecations diff --git a/cmtool-assemble/src/data.rs b/cmtool-assemble/src/data.rs index 01bdc6ca..0ed27333 100644 --- a/cmtool-assemble/src/data.rs +++ b/cmtool-assemble/src/data.rs @@ -9,6 +9,7 @@ pub enum FlowDirection { Out, } +///Required information to describe a feed #[derive(Debug, Serialize, Deserialize, Clone, Copy)] pub struct FeedFlow { pub flow: f64, @@ -16,16 +17,24 @@ pub struct FeedFlow { pub output_position: Option, } +///Set of feed for each phase and their id #[derive(Default, Clone, Serialize, Deserialize)] pub struct ParsedFeeds { pub liq: HashMap, pub gas: HashMap, } + +///Basic about domain +//TODO: clean what's should be private or not #[derive(Default, Clone, Serialize, Deserialize)] pub struct DomainInfo { + ///Compartment index offset for given reactor (e.g compartment_cumsum[reactor_id]==10) pub compartment_cumsum: HashMap, pub total_number_compartment: usize, + ///Reactor id of pfr names, needed to ensure global mass balance pub pfr_names: Vec, + //TODO improve it + ///Indicates if case only contains cfd-based reator pub cm_case_only: Option, pub is_two_phase_flow: bool, } @@ -41,10 +50,14 @@ impl DomainInfo { } } +///Details about generated domain +//TODO: clean what's should be private or not #[derive(Clone, Serialize, Deserialize, Default)] pub struct DomainData { + ///connections between partial flowmaps pub connections: Option<[cmtool_data::RawDataFlux; 2]>, pub info: DomainInfo, + ///Information about feed of the resulting merged domain pub feeds: Option, pub case_path: String, pub run_id: String, diff --git a/cmtool-assemble/src/generators.rs b/cmtool-assemble/src/generators.rs index 71907fb2..01cd9bef 100644 --- a/cmtool-assemble/src/generators.rs +++ b/cmtool-assemble/src/generators.rs @@ -27,6 +27,7 @@ pub struct PFRDescription { pub axial_dispersion: f64, } +//TODO improve and change name pub struct Generator { raw_phase: Vec, } @@ -37,7 +38,7 @@ struct Field0D { #[allow(unused)] value: f64, } - +///wrapper Get absolute path from relative fn resolve_path( case: &CMCase, relative_path: &str, @@ -47,6 +48,7 @@ fn resolve_path( .ok_or(CMError::Custom("Error resolving path".to_string())) } +///Create vector of raw phase from raw fn get_raw_phase( flows: Vec, vol: Vec, @@ -63,6 +65,7 @@ fn get_raw_phase( .collect() } +///Select specific phase type in a slice of phases fn filter_phase(raw_phase: &[RawPhase], phase: PhaseCM) -> Vec { raw_phase .iter() @@ -349,27 +352,28 @@ impl Generator { let mut n_div = [0, 0, 0]; for id in ids.iter() { - let case = CMCaseJson::read_case(Path::new(&format!("{}/{}/cma_case", dest, id)))?; + let partial_case = + CMCaseJson::read_case(Path::new(&format!("{}/{}/cma_case", dest, id)))?; let relative_path = format!("{}/{}", dest, id); let (liquid_flow, liquid_volume) = PAIRS.0; - let path = resolve_path(&case, &relative_path, liquid_flow)?; + let path = resolve_path(&partial_case, &relative_path, liquid_flow)?; let rf = RawDataFlux::read_raw(path).ok_or(CMError::Custom("Error reading".to_string()))?; let n_zone = rf.header.n_zone as usize; liquid_flows.push(rf); - n_div[0] += case.n_div[0]; - n_div[1] += case.n_div[1]; - n_div[2] += case.n_div[2]; + n_div[0] += partial_case.n_div[0]; + n_div[1] += partial_case.n_div[1]; + n_div[2] += partial_case.n_div[2]; - let path = resolve_path(&case, &relative_path, liquid_volume)?; + let path = resolve_path(&partial_case, &relative_path, liquid_volume)?; let sc = RawDataScalar::read_raw(path) .ok_or(CMError::Custom("Error reading".to_string()))?; liquid_volumes.push(sc); let (gas_flow, gas_volume) = PAIRS.1; - let path = resolve_path(&case, &relative_path, gas_flow)?; + let path = resolve_path(&partial_case, &relative_path, gas_flow)?; let rf = match RawDataFlux::read_raw(path) { Some(rf) => rf, @@ -380,7 +384,7 @@ impl Generator { }; gas_flows.push(rf); - let path = resolve_path(&case, &relative_path, gas_volume)?; + let path = resolve_path(&partial_case, &relative_path, gas_volume)?; let rs = match RawDataScalar::read_raw(path) { Some(rs) => rs, None => { diff --git a/cmtool-assemble/src/lib.rs b/cmtool-assemble/src/lib.rs index 9bf829fb..731ab842 100644 --- a/cmtool-assemble/src/lib.rs +++ b/cmtool-assemble/src/lib.rs @@ -13,7 +13,7 @@ pub use data::{FeedFlow, ParsedFeeds}; use map_generation::generate_flowmap; use thiserror::Error; -pub use cmtool_data::PhaseCM; //reexport to have easier dependency +pub use cmtool_data::PhaseCM; //reexport to have easier dependency #[derive(Error, Debug)] pub enum CMError { @@ -40,13 +40,20 @@ pub enum CMError { Parse(#[from] serde_xml_rs::Error), } +/// Domain parser +// TODO: make it private + change name pub struct Parser(RootElementType); impl Parser { + /// Create parser from xml content + /// Returns id + object if suceeds pub fn start_parsing(reactor_content: &str) -> Result<(String, Self), CMError> { let root = get_root(reactor_content)?; Ok((root.run_id.clone(), Self(root))) } + + /// Parse domain and generate content in the root directory if needed + /// Returns info about generated domain if suceeds fn continue_parsing(p: Parser, root_dir: &str) -> Result { // let (domain, mb) = parse_domain(&root)?; // let root_dir = format!("{}/{}", root_dir, root.run_id); @@ -57,6 +64,8 @@ impl Parser { Self::continue_parsing_with_path(p, path.as_str()) } + /// Parse domain and generate content at given abolute path if needed + /// Returns info about generated domain if suceeds pub fn continue_parsing_with_path( Parser(root): Parser, root_dir: &str, @@ -66,6 +75,7 @@ impl Parser { let path = if let Some(cm_case) = &domain.info.cm_case_only { cm_case.clone() } else { + //TODO: Do not create all, return error if not root_dir std::fs::create_dir_all(root_dir)?; generate_flowmap(root_dir, &domain, &root.reactors, &mb)? }; @@ -75,6 +85,8 @@ impl Parser { } } +//Parse and generate domain at root dir from give xml content +// Returns info about domain if suceeds pub fn generate_domain(root_dir: &str, reactor_content: &str) -> Result { let (_id, root) = Parser::start_parsing(reactor_content)?; @@ -83,11 +95,15 @@ pub fn generate_domain(root_dir: &str, reactor_content: &str) -> Result, ) -> Result<(), CMError> { let contents = std::fs::read_to_string(reactor_input_file_name)?; + //Useless because already created in continue_parsing_with_path + //TODO: keep it here and returns errors if not exist let root_dir = path.as_ref().to_str().expect("path str").to_owned(); std::fs::create_dir_all(&root_dir)?; generate_domain(&root_dir, &contents)?; diff --git a/cmtool-assemble/src/map_generation.rs b/cmtool-assemble/src/map_generation.rs index b47db88b..4660112c 100644 --- a/cmtool-assemble/src/map_generation.rs +++ b/cmtool-assemble/src/map_generation.rs @@ -3,19 +3,22 @@ use crate::CMError; use crate::data::DomainData; use crate::generators::{Generator, PFRDescription}; -use crate::parser::generated_domain; +use crate::parser::generated_domain::{self, GeneralSizeType}; use crate::parser::{PfrGlobalMassBalance, generated_domain::Reactor0DType}; use cmtool_data::{CMCaseJson, CMCaseReader, PhaseCM}; use cmtool_data::{CMCaseWriter, DataError}; -fn get_volume(size: &generated_domain::GeneralSizeType) -> f64 { - match &size { - generated_domain::GeneralSizeType::Volume(v) => v.content as f64, - generated_domain::GeneralSizeType::Dimension(dim) => { - (dim.length.content as f64) - * (dim.diameter.content.powf(2.) as f64) - * std::f64::consts::PI - / 4. +impl GeneralSizeType { + ///Returns volume of reactor considering cylindrical shape + pub fn get_volume(&self) -> f64 { + match self { + generated_domain::GeneralSizeType::Volume(v) => v.content as f64, + generated_domain::GeneralSizeType::Dimension(dim) => { + (dim.length.content as f64) + * (dim.diameter.content.powf(2.) as f64) + * std::f64::consts::PI + / 4. + } } } } @@ -28,7 +31,7 @@ fn _generate_reactor_0d( reactor0d: &Reactor0DType, ) -> Result<(), CMError> { ids.push(reactor0d.id.clone()); - let volume = get_volume(&reactor0d.size); + let volume = reactor0d.size.get_volume(); let path = format!("{}/{}", root, reactor0d.id); let mut opt_path = None; @@ -115,7 +118,7 @@ fn generate_partial_flowmap( todo!("{:?}", reactor3_dtype) } generated_domain::ReactorsTypeContent::ReactorFromFile(_) => { - todo!("") + todo!("generate_partial_flowmap::ReactorFromFile") } } } diff --git a/cmtool-assemble/src/parser/mod.rs b/cmtool-assemble/src/parser/mod.rs index 0b8ff6d8..b3e5ce3b 100644 --- a/cmtool-assemble/src/parser/mod.rs +++ b/cmtool-assemble/src/parser/mod.rs @@ -1,13 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-or-later - - #[rustfmt::skip] pub mod generated_domain; use crate::{CMError, DomainData}; mod reactors; use reactors::{parse_connection, parse_feed, parse_reactor}; - mod pfr_mb; pub(super) use pfr_mb::PfrGlobalMassBalance; @@ -48,9 +45,6 @@ pub fn parse_domain( } pub fn get_root(content: &str) -> Result { - // let cursor = Cursor::new(content.as_bytes()); - // let mut reader = IoReader::new(cursor).with_error_info(); - // let root = generated_domain::Root::deserialize(&mut reader).unwrap(); let root = serde_xml_rs::from_str::(content)?; eprintln!("WARNING: Some reactor may miss if xml is not parsed correctly"); if root.version != 3 { diff --git a/cmtool-assemble/src/parser/pfr_mb.rs b/cmtool-assemble/src/parser/pfr_mb.rs index a0aca26f..a413d527 100644 --- a/cmtool-assemble/src/parser/pfr_mb.rs +++ b/cmtool-assemble/src/parser/pfr_mb.rs @@ -1,24 +1,24 @@ // SPDX-License-Identifier: GPL-3.0-or-later -use std::collections::HashMap; - -use cmtool_data::PhaseCM; - use crate::{CMError, data::FlowDirection}; +use cmtool_data::PhaseCM; +use std::collections::HashMap; +///Input and output flow for one phase in one pfr #[derive(Default, Copy, Clone, Debug)] struct FlowData { in_flow: f64, out_flow: f64, } +/// flow data for one pfr #[derive(Copy, Clone, Debug, Default)] pub struct PhaseFlow { gas: FlowData, liquid: FlowData, } -// Struct to hold flow data for each ID using a HashMap +/// Struct to hold flow data for each ID using a HashMap #[derive(Default, Debug)] pub struct PfrGlobalMassBalance { flows: HashMap, diff --git a/cmtool-assemble/src/parser/reactors.rs b/cmtool-assemble/src/parser/reactors.rs index 52620b28..9dbadb34 100644 --- a/cmtool-assemble/src/parser/reactors.rs +++ b/cmtool-assemble/src/parser/reactors.rs @@ -8,6 +8,7 @@ use super::generated_domain; use crate::data::FeedFlow; use crate::data::ParsedFeeds; use crate::parser::generated_domain::FeedFluxType; +use crate::parser::generated_domain::FluxType; use crate::{ CMError, data::{DomainInfo, FlowDirection}, @@ -86,51 +87,27 @@ pub fn parse_connection( ] } -// fn convert_feed_flux_to_flux(feed: generated_domain::FeedFluxType) -> generated_domain::FluxType { -// generated_domain::FluxType { -// source: feed.source, -// target: feed.target, -// phase: feed.phase, -// value: feed.value, -// } -// } - -// pub fn parse_feed( -// info: &DomainInfo, -// feeds: &generated_domain::FeedsType, -// mass_balance: &mut PfrGlobalMassBalance, -// ) -> Result<(), CMError> { -// let liquid_feed: Vec = feeds -// .flux -// .iter() -// .filter(|f| f.phase == *"liquid") -// .map(|feed| convert_feed_flux_to_flux(feed.clone())) -// .collect(); -// let gas_feed: Vec = feeds -// .flux -// .iter() -// .filter(|f| f.phase == *"gas") -// .map(|feed| convert_feed_flux_to_flux(feed.clone())) -// .collect(); -// connection_per_phase(info, mass_balance, &liquid_feed, PhaseCM::Liquid); -// connection_per_phase(info, mass_balance, &gas_feed, PhaseCM::Gas); -// Ok(()) -// } +///Flux type is a "derivated" type of flux with all flux information + the flow value +///Current implementation performs naive copy of common attributes. +impl From<&FeedFluxType> for FluxType { + fn from(feed: &FeedFluxType) -> Self { + Self { + source: feed.source.clone(), + target: feed.target.clone(), + phase: feed.phase.clone(), + value: feed.value.clone(), + } + } +} + fn parse_feed_phase( info: &DomainInfo, feeds: &[&FeedFluxType], phase: PhaseCM, mass_balance: &mut PfrGlobalMassBalance, ) -> HashMap { - let fluxes: Vec = feeds - .iter() - .map(|feed| generated_domain::FluxType { - source: feed.source.clone(), - target: feed.target.clone(), - phase: feed.phase.clone(), - value: feed.value.clone(), - }) - .collect(); + let fluxes: Vec = + feeds.iter().map(|&feed| FluxType::from(feed)).collect(); let id: Vec = feeds.iter().map(|feed| feed.id.clone()).collect(); let rd = connection_per_phase(info, mass_balance, &fluxes, phase); @@ -150,6 +127,7 @@ fn parse_feed_phase( }) .collect() } + pub fn parse_feed( info: &DomainInfo, feeds: &generated_domain::FeedsType, diff --git a/cmtool-cxx/src/lib.rs b/cmtool-cxx/src/lib.rs index ca379376..0bc1118e 100644 --- a/cmtool-cxx/src/lib.rs +++ b/cmtool-cxx/src/lib.rs @@ -1,11 +1,17 @@ // SPDX-License-Identifier: GPL-3.0-or-later -use std::ptr::null; +//!Expose required method to C++ use cmtool_data::{ DiscontinuousTransitioner, FlowMapTransitioner, HydroState, IterationState, get_transitioner, }; use nalgebra_sparse::CooMatrix; + +use std::ptr::null; + +//Choice to use raw ptr was made to avoid clone/creating Arc which are useless for our usage +//All ptr are valid because they are created from rust side + struct TransitionerWrapper(DiscontinuousTransitioner); struct IterationStateWrapper(*const IterationState); diff --git a/cmtool-data/src/descriptors.rs b/cmtool-data/src/descriptors.rs index a03b04cc..bfcae8fd 100644 --- a/cmtool-data/src/descriptors.rs +++ b/cmtool-data/src/descriptors.rs @@ -1,6 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-or-later use serde::{Deserialize, Serialize}; + +///Legacy type of exported type value in a CM model +// #[deprecated(since = "0.1.4", note = "use CMExportType")] +//TODO remove where possible #[derive(Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Clone, Copy)] pub enum CMAExportType { LiquidFlow, @@ -12,6 +16,7 @@ pub enum CMAExportType { Other, } +///Phase name in compartment model #[derive(Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Clone, Copy)] pub enum PhaseCM { Liquid, @@ -42,6 +47,7 @@ impl From for PhaseCM { } } +///Enum-Struct based improvement of 'CMAExportType' pub enum CMExportType { Flow(PhaseCM), Volume(PhaseCM), diff --git a/cmtool-data/src/lib.rs b/cmtool-data/src/lib.rs index bf214b4c..ac9766c4 100644 --- a/cmtool-data/src/lib.rs +++ b/cmtool-data/src/lib.rs @@ -18,6 +18,7 @@ pub use states::*; use std::io; use thiserror::Error; pub use transitioner::*; + /// Errors that can occur during data operations. /// /// This enum encapsulates various error conditions that might arise during @@ -62,6 +63,7 @@ fn linear_index_col_major(n_row: usize, _n_col: usize, i: usize, j: usize) -> us j * n_row + i } +///Create transitioner pub fn get_transitioner(root: &str) -> Result { let case_path = format!("{}/cma_case", root); let p = std::path::Path::new(&case_path); @@ -71,6 +73,7 @@ pub fn get_transitioner(root: &str) -> Result(fmt: &T) -> f64 { let n_states = fmt.size(); let mut min_all = f64::MAX; diff --git a/cmtool-data/src/rawdata.rs b/cmtool-data/src/rawdata.rs index 4e7219ce..612a89cb 100644 --- a/cmtool-data/src/rawdata.rs +++ b/cmtool-data/src/rawdata.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later +use crate::DataError; use crate::descriptors::{CMExportType, PhaseCM}; use serde::{Deserialize, Serialize}; use std::{ @@ -7,8 +8,9 @@ use std::{ io::{Read, Write}, path::{Path, PathBuf}, }; -pub type ScalarValueType = f64; -use crate::DataError; + +///Scalar type (float) +pub type ScalarValueType = f64; //TODO decide if this alias is needed // A trait for reading and writing raw data to and from storage. pub trait RawData: Sized { @@ -98,7 +100,7 @@ pub struct ScalarFileHeader { #[derive(Deserialize, Serialize, Clone, Copy)] pub struct RawScalar { /// The scalar value stored as a floating-point number. - pub value: f64, + pub value: ScalarValueType, } /// Represents a collection of raw scalar data along with its header. @@ -173,9 +175,9 @@ impl Default for RawFlux { } } -impl From for RawScalar { +impl From for RawScalar { #[inline(always)] - fn from(value: f64) -> Self { + fn from(value: ScalarValueType) -> Self { Self { value } } } @@ -191,14 +193,14 @@ impl RawDataScalar { } } -impl From> for RawDataScalar { - fn from(value: Vec) -> Self { +impl From> for RawDataScalar { + fn from(value: Vec) -> Self { value.as_slice().into() } } -impl From<&[f64]> for RawDataScalar { - fn from(value: &[f64]) -> Self { +impl From<&[ScalarValueType]> for RawDataScalar { + fn from(value: &[ScalarValueType]) -> Self { let len: u32 = value.len().try_into().unwrap_or_else(|_| { panic!("Array length is too large to convert into u32"); }); @@ -343,15 +345,15 @@ impl ToBytes for FluxFileHeader { impl FromBytes for RawScalar { fn from_bytes(buffer: &[u8], offset: &mut usize) -> Option { - if *offset + size_of::() > buffer.len() { + if *offset + size_of::() > buffer.len() { return None; } - let value = f64::from_le_bytes( - buffer[*offset..*offset + size_of::()] + let value = ScalarValueType::from_le_bytes( + buffer[*offset..*offset + size_of::()] .try_into() .unwrap(), ); - *offset += size_of::(); + *offset += size_of::(); Some(RawScalar { value }) } } diff --git a/cmtool-data/src/transitioner/mod.rs b/cmtool-data/src/transitioner/mod.rs index a209f2c4..7e6923c2 100644 --- a/cmtool-data/src/transitioner/mod.rs +++ b/cmtool-data/src/transitioner/mod.rs @@ -107,9 +107,13 @@ pub trait FlowMapTransitioner { } } +///Type of available transitionner pub enum TransitionerType { + ///Time based discontinous transition, easier to manipulate Discontinuous, + ///Index based discontinous transitionner, alway keep current state Simple, + None, } diff --git a/documentations/index.html b/documentations/index.html index 6428509d..16cf5df6 100644 --- a/documentations/index.html +++ b/documentations/index.html @@ -47,7 +47,7 @@

Useful Resources

- © 2025 CMTool. All Rights Reserved. | Version 0.1.2 | + © 2025 CMTool. All Rights Reserved. | Version 0.1.4 |

From b7362c46210d9571a55654c96c5c1e6a046f9161 Mon Sep 17 00:00:00 2001 From: bcasale Date: Mon, 16 Mar 2026 16:53:51 +0100 Subject: [PATCH 30/44] feat(vtk): python method to create vtk file --- cmtool-python/pycmtool/export_vtk.py | 233 +++++++++++++++++++++++++++ cmtool/src/main.rs | 95 ++--------- cmtool/src/sanitizer.rs | 6 +- examples/python/mixing_simple.py | 3 +- examples/python/simple_export.py | 19 +++ 5 files changed, 273 insertions(+), 83 deletions(-) create mode 100644 cmtool-python/pycmtool/export_vtk.py create mode 100644 examples/python/simple_export.py diff --git a/cmtool-python/pycmtool/export_vtk.py b/cmtool-python/pycmtool/export_vtk.py new file mode 100644 index 00000000..9ea3aa0c --- /dev/null +++ b/cmtool-python/pycmtool/export_vtk.py @@ -0,0 +1,233 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +""" +This file is part of Compartment Modelling Tool Project (CMT). + +Compartment Modelling Tool Project (CMT) is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Compartment Modelling Tool Project (CMT) is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with Compartment Modelling Tool Project (CMT). If not, see . + +Please contact: + +- Casale Benjamin: casale@insa-toulouse.fr +""" + +__all__ = [] +import json +import os +from typing import Dict, List + +import numpy as np +import vtk +import vtkmodules.util.numpy_support as npvtk + +# from cmtool.wcma_read import RawDataFlux + +# def mk_point(coordinates): +# points = vtk.vtkPoints() +# for coordinate in coordinates: +# points.InsertNextPoint(coordinate) +# return points + + +# def save_points(filename,coordinates): +# points = mk_point(coordinates) +# polydata = vtk.vtkPolyData() +# polydata.SetPoints(points) + +# # Write the PolyData to a VTK XML file +# writer = vtk.vtkXMLPolyDataWriter() +# writer.SetFileTypeToBinary() +# writer.SetFileName(f"{filename}.vtp") +# writer.SetInputData(polydata) +# writer.Update() +# writer.Write() + + +# def write_vtk(filename,coordinates,*args): +# points = mk_point(coordinates) +# polydata = vtk.vtkPolyData() +# polydata.SetPoints(points) + +# for i in args: +# polydata.GetPointData().AddArray(i) + +# # Write the PolyData to a VTK XML file +# writer = vtk.vtkXMLPolyDataWriter() +# writer.SetFileName(filename) +# writer.SetInputData(polydata) +# writer.SetDataModeToBinary() +# writer.Update() +# writer.Write() + + +def mk_scalar(np_array_data: np.ndarray, scalar_name: str): + """! + @brief Make VTK scalar from numpy array. + + This function converts a numpy array into a VTK scalar array. It sets the number of components to 1 + and assigns the specified scalar name. + + @param np_array_data (numpy.ndarray): The input numpy array containing scalar data. + @param scalar_name (str): The name to assign to the VTK scalar array. + + @return vtk.vtkFloatArray: The VTK scalar array containing the data. + + @example + import numpy as np + from cmtool.vtk import mk_scalar + + number_of_cell = ... + scalar = np.zeros((number_of_cell,)) + mk_scalar(scalar,"my_scalar") + """ + scalar_array = npvtk.numpy_to_vtk(np_array_data) + scalar_array.SetNumberOfComponents(1) + scalar_array.SetName(scalar_name) + return scalar_array + + +def read_scalar(filename, name) -> np.ndarray: + """! + @brief Read scalar data from a VTK XML unstructured grid file. + + This function reads scalar data from a VTK XML unstructured grid file. It retrieves the scalar + data associated with the specified name from the cell data of the grid. + + @param filename str: The path to the VTK XML unstructured grid file. + @param name str: The name of the scalar data array to be retrieved. + + @return numpy.ndarray: The scalar data as a numpy array. + + @example + scalar_data = read_scalar("example.vtu", "Pressure") + """ + reader = vtk.vtkXMLUnstructuredGridReader() + reader.SetFileName(filename) + # Perform the read operation + reader.Update() + grid = reader.GetOutput() + vtk_array = grid.GetCellData().GetArray(name) + return npvtk.vtk_to_numpy(vtk_array) + + +def append_scalar(filename, destination, *args): + """! + @brief Append scalar arrays to an existing VTK XML unstructured grid file. + + This function reads an existing VTK XML unstructured grid file, appends the specified scalar arrays to the cell data, + and writes the modified grid to a new VTK XML file. + + @param filename str: The path to the input VTK XML unstructured grid file. + @param destination str: The path to the output VTK XML unstructured grid file. + @param args vtk.vtkDataArray: Variable-length list of VTK scalar arrays to append to the cell data. + + @return None + + @example + pressure_array = vtk.vtkFloatArray() + pressure_array.SetName("Pressure") + # ...populate pressure_array with data... + append_scalar("input.vtu", "output.vtu", pressure_array) + """ + # Read the input VTK XML unstructured grid file + reader = vtk.vtkXMLUnstructuredGridReader() + reader.SetFileName(filename) + reader.Update() + grid = reader.GetOutput() + + # Append scalar arrays to the cell data + for scalar_array in args: + grid.GetCellData().AddArray(scalar_array) + + # Write the modified grid to a new VTK XML file + writer = vtk.vtkXMLUnstructuredGridWriter() + writer.SetFileName(destination) + writer.SetInputData(grid) + writer.SetDataModeToBinary() + writer.Update() + writer.Write() + + +def write_json_series( + destination: str, name: str, file_entries: List[Dict[str, str]] +) -> None: + """! + @brief Write a JSON file for a VTK file series. + + This function creates a JSON file that describes a series of VTK files. It includes + the version of the file-series and a list of file entries provided by the user. + + @param destination str: The directory path where the JSON file will be saved. + @param name str: The base name for the JSON file (without extension). + @param file_entries list: A list of file entries (each entry is a dictionary) that specifies the files in the series. + + @return None + + @example + file_entries = [ + {"name": "step0.vtu", "time": 0.0}, + {"name": "step1.vtu", "time": 1.0} + ] + write_json_series("/path/to/destination", "series_name", file_entries) + """ + json_content = {"file-series-version": "1.0", "files": file_entries} + + # Write the JSON file + json_file_path = f"{destination}/{name}.vtu.series" + with open(json_file_path, "w") as json_file: + json.dump(json_content, json_file, indent=4) + + +def mk_series(filepath: str, destination: str, series_name: str, t: np.ndarray, *args): + """! + @brief Generate a series of VTK files and a corresponding JSON series file. + + This function creates a series of VTK XML unstructured grid files (.vtu) and a JSON file that describes + the series. For each time step provided in the `t` array, it generates a VTK file with appended scalar + data arrays and saves these files in the specified destination directory. It also generates a JSON file + to represent the file series. + + @param filepath str: The path to the input VTK XML unstructured grid file. + @param destination str: The directory where the series files and JSON descriptor will be saved. + @param series_name str: The base name for the series files and the JSON descriptor. + @param t numpy.ndarray: An array of time steps corresponding to each VTK file in the series. + @param args Variable-length argument list of tuples, where each tuple contains: + - data numpy.ndarray: The scalar data array to be appended to each VTK file. + - name str: The name of the scalar data array. + + @return None + + @example + t = np.array([0.0, 1.0, 2.0]) + pressure_data = np.random.random((3, 100)) # Example scalar data with 3 time steps and 100 cells + temperature_data = np.random.random((3, 100)) # Another example scalar data + mk_series("input.vtu", "output_directory", "simulation_series", t, (pressure_data, "Pressure"), (temperature_data, "Temperature")) + """ + file_entries = [] + n_t = len(t) + root = f"{destination}/{series_name}" + if not os.path.exists(root): + os.makedirs(root) + + for i in range(n_t): + vtp_result = f"{root}/{series_name}_{i}.vtu" + scalar = [] + for s in args: + data = s[0] + name = s[1] + scalar.append(mk_scalar(data[i], name)) + append_scalar(filepath, vtp_result, *scalar) + file_entries.append({"name": f"{series_name}_{i}.vtu", "time": t[i]}) + write_json_series(root, series_name, file_entries) + + +__all__.extend(["mk_series", "read_scalar", "write_json_series", "mk_scalar"]) diff --git a/cmtool/src/main.rs b/cmtool/src/main.rs index 76808353..3b0ca9ae 100644 --- a/cmtool/src/main.rs +++ b/cmtool/src/main.rs @@ -64,6 +64,8 @@ fn auto_main(common: CommonArgs, autoargs: AutoArgs) -> Result<(), CmtoolError> .dump_all(format!("{}/{}", root_dir, stem), &case.root, &case.paths) .map_err(CmtoolError::Core)?; + handle.dump_real_volume(format!("{}/{}/vofL", root_dir, stem))?; + #[cfg(feature = "use_vtk")] handle.write_vtk(format!("{}/{}/cma_case.vtu", root_dir, stem)); @@ -72,84 +74,19 @@ fn auto_main(common: CommonArgs, autoargs: AutoArgs) -> Result<(), CmtoolError> ) .unwrap(); println!("{}", f); - // return Ok(()); - Ok(()) + // std::fs::write("/tmp/checks.csv", f); - // let p1 = "/home/benjamin/Documents/thesis/cfd-cma/sanofi_cfd/inputs/RESULTS.scl1"; - // let p2 = "/home/benjamin/Documents/thesis/cfd-cma/sanofi_cfd/inputs/RESULTS.scl2"; - // let p3 = "/home/benjamin/Documents/thesis/cfd-cma/sanofi_cfd/inputs/RESULTS.scl3"; - // // - // let export = handle - // .dump_vector_from_scalar(format!("{}/flowL", root_dir), p1, p2, p3) - // .unwrap(); - - // if let Some(mut check_csv) = cmtool::check_flows(&export) { - // check_csv - // .write_str(&format!("sanitize_{}.csv", stem)) - // .unwrap(); - // }; - - // Ok(()) - - // cmtool::check_flows(&RawDataFlux::read_raw( - // "./out/cuve_sldmsh_initmrf/axial_velocity.raw", - // ).unwrap()); - - // cmtool::check_flows(&RawDataFlux::read_raw( - // "/home/benjamin/Documents/thesis/cfd-cma/sanofi/raw/flowL.raw", - // ).unwrap()); -} + // let mut case = cmtool_data::CMCase::new( + // [common.n_i as u32, common.n_j as u32, common.n_k as u32], + // 0., + // None, + // false, + // ); -// fn main() { -// // #[cfg(debug_assertions)] -// let args = GenArgs { -// case_path: "/home/benjamin/Documents/thesis/cfd-cma/rushton/cuve_sldmsh_initmrf.encas" -// .to_string(), -// n_i: 10, -// n_j: 10, -// n_k: 5, -// out: None, -// }; - -// //#[cfg(not(debug_assertions))] -// //let args = GenArgs::parse(); - -// let stem = Path::new(&args.case_path) -// .file_stem() // Gets "mycase" as OsStr -// .and_then(|s| s.to_str()) -// .unwrap(); // Converts OsStr to &str - -// let case = cmtool_core::ensight_gold::Case::read(&args.case_path).unwrap(); - -// let root_dir = args -// .out -// .unwrap_or(format!("{}/../out/", env!("CARGO_MANIFEST_DIR"))); - -// std::fs::create_dir_all(&root_dir).unwrap(); - -// let handle = cmtool_core::CMHandle::init( -// [args.n_i, args.n_j, args.n_k], -// &case.root, -// &case.geometry_file_path, -// cmtool_core::grid::MeshType::Cylindrical, -// ) -// .unwrap(); - -// handle -// .dump_all(format!("{}/{}", root_dir, stem), &case.root, &case.paths) -// .unwrap(); -// handle -// .dump_real_volume(format!("{}/{}/total_volume", root_dir, stem)) -// .unwrap(); -// // let p1 = "/home/benjamin/Documents/thesis/cfd-cma/sanofi_cfd/inputs/RESULTS.scl1"; -// // let p2 = "/home/benjamin/Documents/thesis/cfd-cma/sanofi_cfd/inputs/RESULTS.scl2"; -// // let p3 = "/home/benjamin/Documents/thesis/cfd-cma/sanofi_cfd/inputs/RESULTS.scl3"; -// // // -// // let export = handle -// // .dump_vector_from_scalar(format!("{}/flowL", root_dir), p1, p2, p3) -// // .unwrap(); - -// // cmtool::check_flows(&export); - -// cmtool::check_flows(&RawDataFlux::read_raw("./out/cuve_sldmsh_initmrf/velocity.raw").unwrap()); -// } + // cmtool_data::CMCaseJson::write_case( + // case, + // std::path::Path::new(&format!("{}/{}/cma_case", root_dir, stem)), + // )?; + + Ok(()) +} diff --git a/cmtool/src/sanitizer.rs b/cmtool/src/sanitizer.rs index 6646c680..38de668a 100644 --- a/cmtool/src/sanitizer.rs +++ b/cmtool/src/sanitizer.rs @@ -53,11 +53,11 @@ pub fn check_flows(raw_flows: &cmtool_data::RawDataFlux) -> Option { let mean_relative_error = total_relative_error / raw_flows.header.n_zone as f64; writeln!(&mut f, "metric,value").unwrap(); - writeln!(&mut f, "total_inflow,{:.6}", total_inflow).unwrap(); - writeln!(&mut f, "total_outflow,{:.6}", total_outflow).unwrap(); + writeln!(&mut f, "total_inflow,{:.8}", total_inflow).unwrap(); + writeln!(&mut f, "total_outflow,{:.8}", total_outflow).unwrap(); writeln!( &mut f, - "global_net_error_percent,{:.6}", + "global_net_error_percent,{:.8}", smape(total_inflow, total_outflow) * 100.0 ) .unwrap(); diff --git a/examples/python/mixing_simple.py b/examples/python/mixing_simple.py index 650cdf2d..13d98e8a 100644 --- a/examples/python/mixing_simple.py +++ b/examples/python/mixing_simple.py @@ -114,6 +114,7 @@ def check_mixing(fmt, final_time: float): m0m = np.sum(m0_c, axis=0) mfm = np.sum(mt_c, axis=0) + print("Total volume: ", np.sum(vol)) print("Inital mass: ", m0m) print("Final mass: ", mfm) print("Initial normalized C: ", c_init[:5]) @@ -133,7 +134,7 @@ def check_mixing(fmt, final_time: float): if __name__ == "__main__": - final_time = 500 + final_time = 50 root = os.environ["EXAMPLE_ROOT"] # Let CMTool read and load the full case automatically, ready to iterate diff --git a/examples/python/simple_export.py b/examples/python/simple_export.py new file mode 100644 index 00000000..05602dc0 --- /dev/null +++ b/examples/python/simple_export.py @@ -0,0 +1,19 @@ +import pycmtool +import pycmtool.export_vtk + +if __name__ == "__main__": + vx = "out/cuve_sldmsh_initmrf/x_velocity.raw" + vy = "out/cuve_sldmsh_initmrf/y_velocity.raw" + vz = "out/cuve_sldmsh_initmrf/z_velocity.raw" + scvx = pycmtool.data.read_rawscalar(vx) + scvy = pycmtool.data.read_rawscalar(vy) + scvz = pycmtool.data.read_rawscalar(vz) + + scvx_vtk = pycmtool.export_vtk.mk_scalar(scvz.data, "v_x") + scvy_vtk = pycmtool.export_vtk.mk_scalar(scvz.data, "v_y") + scvz_vtk = pycmtool.export_vtk.mk_scalar(scvz.data, "v_z") + + vtu_path = "out/cuve_sldmsh_initmrf/cma_case.vtu" + pycmtool.export_vtk.append_scalar( + vtu_path, "/tmp/test.vtu", scvx_vtk, scvx_vtk, scvy_vtk, scvz_vtk + ) From d2cc6c3973b4836bcb739245e7965735130aeb41 Mon Sep 17 00:00:00 2001 From: bcasale Date: Mon, 16 Mar 2026 16:54:50 +0100 Subject: [PATCH 31/44] refractor --- cmtool-core/src/coordinates/vec3.rs | 2 +- cmtool-core/src/lib.rs | 62 ++++++++++++++--------------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/cmtool-core/src/coordinates/vec3.rs b/cmtool-core/src/coordinates/vec3.rs index 4fea7389..33f1c350 100644 --- a/cmtool-core/src/coordinates/vec3.rs +++ b/cmtool-core/src/coordinates/vec3.rs @@ -2,7 +2,7 @@ use crate::coordinates::{CartesianCoordinates, Coords3}; -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub struct CartesianVec3(pub Coords3); #[derive(Clone, Copy)] diff --git a/cmtool-core/src/lib.rs b/cmtool-core/src/lib.rs index 1e532365..1923a4d3 100644 --- a/cmtool-core/src/lib.rs +++ b/cmtool-core/src/lib.rs @@ -17,6 +17,13 @@ use cmtool_data::{RawData, RawDataFlux, RawDataScalar}; use model::{CMGeometry, CMModel, Scalar, Vector}; use std::{path::Path, sync::Arc}; +fn resolve_path( + root: &impl AsRef, + relative_path: &str, +) -> impl AsRef { + std::path::PathBuf::from(root.as_ref()).join(relative_path) +} + pub enum ExportType { EnsightGold, } @@ -35,13 +42,6 @@ pub struct CMHandle { cm_geometry: Arc, } -fn resolve_path( - root: &impl AsRef, - relative_path: &str, -) -> impl AsRef { - std::path::PathBuf::from(root.as_ref()).join(relative_path) -} - impl CMHandle { pub fn init( n_div: [usize; 3], @@ -86,30 +86,6 @@ impl CMHandle { todo!() } - #[cfg(feature = "use_vtk")] - pub fn write_vtk(&self, path: impl AsRef) -> Result<(), CoreError> { - let mesh = self.cm_geometry.get_grid().unwrap(); - let p = path.as_ref().to_str().unwrap(); - let mut vtk = mesh.get_vtk(p)?; - - let volumes_data = self.model.get_real_volume(); - - let volumes_data_array = vtkio::model::DataArray::scalars("real_volume", 1); - - let volumes_data_array = volumes_data_array.with_vec(volumes_data); - - add_celldata_to_vtk( - &mut vtk, - vtkio::model::Attribute::DataArray(volumes_data_array), - ); - - let mut vtk_bytes = Vec::::new(); - vtk.write_xml(&mut vtk_bytes).unwrap(); - std::fs::write(path, vtk_bytes).unwrap(); - - Ok(()) - } - pub fn dump_all( &self, root_export: impl AsRef, @@ -249,4 +225,28 @@ impl CMHandle { pub fn export_geometry_compartments(&self) { todo!() } + + #[cfg(feature = "use_vtk")] + pub fn write_vtk(&self, path: impl AsRef) -> Result<(), CoreError> { + let mesh = self.cm_geometry.get_grid().unwrap(); + let p = path.as_ref().to_str().unwrap(); + let mut vtk = mesh.get_vtk(p)?; + + let volumes_data = self.model.get_real_volume(); + + let volumes_data_array = vtkio::model::DataArray::scalars("real_volume", 1); + + let volumes_data_array = volumes_data_array.with_vec(volumes_data); + + add_celldata_to_vtk( + &mut vtk, + vtkio::model::Attribute::DataArray(volumes_data_array), + ); + + let mut vtk_bytes = Vec::::new(); + vtk.write_xml(&mut vtk_bytes).unwrap(); + std::fs::write(path, vtk_bytes).unwrap(); + + Ok(()) + } } From 3966907bfcf404ca1e9ad3bae1754fccdd2ebefe Mon Sep 17 00:00:00 2001 From: bcasale Date: Mon, 16 Mar 2026 17:35:17 +0100 Subject: [PATCH 32/44] fix(cm): work in progress improve flux calculation --- cmtool-core/src/coordinates/mod.rs | 80 ++++++----- cmtool-core/src/grid/mod.rs | 197 +++++++++++++++++++++++----- cmtool-core/src/model/interfaces.rs | 36 +---- cmtool-core/src/model/mod.rs | 27 ++-- cmtool-core/src/utils/area.rs | 168 +++++++++++++++++------- examples/python/mixing_simple.py | 2 +- 6 files changed, 348 insertions(+), 162 deletions(-) diff --git a/cmtool-core/src/coordinates/mod.rs b/cmtool-core/src/coordinates/mod.rs index 5199a5c8..0886df74 100644 --- a/cmtool-core/src/coordinates/mod.rs +++ b/cmtool-core/src/coordinates/mod.rs @@ -8,19 +8,6 @@ pub use vec3::*; pub const NUMBER_OF_AXIS: usize = 3; pub type Coords3 = [f64; NUMBER_OF_AXIS]; -pub struct Plane { - pub normal: CartesianVec3, - pub point: CartesianCoordinates, -} - -pub struct BoundedPlane { - pub normal: CartesianVec3, - pub origin: CartesianCoordinates, - pub extent_u: [f64; 2], - pub extent_v: [f64; 2], - pub axis: usize, // 0 = r, 1 = theta, 2 = z -} - pub fn orthonormal_basis(normal: &CartesianVec3) -> (CartesianVec3, CartesianVec3) { let n = normal.normalized(); let temp = if n.0[0].abs() < 0.9 { @@ -34,46 +21,57 @@ pub fn orthonormal_basis(normal: &CartesianVec3) -> (CartesianVec3, CartesianVec (u, v) } -impl BoundedPlane { - pub fn is_point_inside(&self, CartesianCoordinates(point): CartesianCoordinates) -> bool { - // let v = CartesianVec3([ - // point[0] - self.origin.0[0], - // point[1] - self.origin.0[1], - // point[2] - self.origin.0[2], - // ]); - - // let (u_dir, v_dir) = orthonormal_basis(&self.normal); - - // let u_proj = v.dot(&u_dir); - // let v_proj = v.dot(&v_dir); +pub struct Plane { + pub normal: CartesianVec3, + pub point: CartesianCoordinates, +} - // (u_proj >= self.extent_u[0] && u_proj <= self.extent_u[1]) - // && (v_proj >= self.extent_v[0] && v_proj <= self.extent_v[1]) +pub struct BoundedPlane { + pub normal: CartesianVec3, + pub origin: CartesianCoordinates, + pub extent_u: [f64; 2], + pub extent_v: [f64; 2], + pub axis: usize, // 0 = r, 1 = theta, 2 = z +} +impl BoundedPlane { + pub fn is_point_inside(&self, CartesianCoordinates(point): CartesianCoordinates) -> bool { let r = (point[0].powi(2) + point[1].powi(2)).sqrt(); - let theta = point[1].atan2(point[0]); + let theta_raw = point[1].atan2(point[0]); let z = point[2]; + let tol = 1e-10; let (u, v) = match self.axis { 0 => { - let u = theta; - let v = z; - (u, v) - } - 1 => { - let u = r; - let v = z; - (u, v) + let pi2 = 2.0 * std::f64::consts::PI; + let mid = 0.5 * (self.extent_u[0] + self.extent_u[1]); + let mut theta = theta_raw; + while theta - mid > std::f64::consts::PI { + theta -= pi2; + } + while mid - theta > std::f64::consts::PI { + theta += pi2; + } + (theta, z) } + 1 => (r, z), 2 => { - let u = r; - let v = theta; - (u, v) + let pi2 = 2.0 * std::f64::consts::PI; + let mid = 0.5 * (self.extent_v[0] + self.extent_v[1]); + let mut theta = theta_raw; + while theta - mid > std::f64::consts::PI { + theta -= pi2; + } + while mid - theta > std::f64::consts::PI { + theta += pi2; + } + (r, theta) } _ => unreachable!(), }; - (u >= self.extent_u[0] && u <= self.extent_u[1]) - && (v >= self.extent_v[0] && v <= self.extent_v[1]) + + (u >= self.extent_u[0] - tol && u <= self.extent_u[1] + tol) + && (v >= self.extent_v[0] - tol && v <= self.extent_v[1] + tol) } } diff --git a/cmtool-core/src/grid/mod.rs b/cmtool-core/src/grid/mod.rs index f6c8570f..91767f58 100644 --- a/cmtool-core/src/grid/mod.rs +++ b/cmtool-core/src/grid/mod.rs @@ -12,30 +12,52 @@ pub(crate) mod vtk; use crate::coordinates::*; use crate::utils::AxisPoints; -fn get_tangent_plane_at_r( - axis: usize, - r0: f64, - theta: f64, - z: f64, - extent_u: [f64; 2], - extent_v: [f64; 2], -) -> BoundedPlane { - let x0 = r0 * theta.cos(); - let y0 = r0 * theta.sin(); - let z0 = z; - - let normal = CartesianVec3([x0 / r0, y0 / r0, 0.0]); - - let origin = CartesianCoordinates([x0, y0, z0]); - - BoundedPlane { - normal, - origin, - extent_u, // extensités sur theta - extent_v, // extensités sur z - axis, - } -} +// fn get_tangent_plane_at_r( +// axis: usize, +// r0: f64, +// theta: f64, +// z: f64, +// extent_u: [f64; 2], +// extent_v: [f64; 2], +// ) -> BoundedPlane { +// let x0 = r0 * theta.cos(); +// let y0 = r0 * theta.sin(); +// let z0 = z; + +// let normal = CartesianVec3([x0 / r0, y0 / r0, 0.0]); + +// let origin = CartesianCoordinates([x0, y0, z0]); + +// BoundedPlane { +// normal, +// origin, +// extent_u, +// extent_v, +// axis, +// } +// } +// +// fn get_tangent_plane_at_r( +// axis: usize, +// r0: f64, +// theta: f64, +// z: f64, +// extent_u: [f64; 2], // [theta0, theta1] — will be converted to arc length +// extent_v: [f64; 2], // [z0, z1] — already metric +// ) -> BoundedPlane { +// let origin = CartesianCoordinates([r0 * theta.cos(), r0 * theta.sin(), z]); +// let normal = CartesianVec3([theta.cos(), theta.sin(), 0.0]); + +// let extent_u_metric = [r0 * extent_u[0], r0 * extent_u[1]]; + +// BoundedPlane { +// normal, +// origin, +// extent_u: extent_u_metric, +// extent_v, // z is already metric +// axis, +// } +// } /// Represents the type of mesh geometry. #[derive(PartialEq, Clone, Copy)] @@ -412,6 +434,90 @@ impl CompartmentMeshManip for MeshCylindrical { interfaces_r + interfaces_theta + interfaces_z + wrap } + // fn get_interface_plane(&self, cell1_id: usize, cell2_id: usize) -> (BoundedPlane, usize) { + // let neighbors = self.are_cell_neighbor(cell1_id, cell2_id); + // let axis = neighbors + // .to_coord_index() + // .expect("Cells must be neighbors to get interface plane"); + + // let sign = if neighbors.is_negative() { -1.0 } else { 1.0 }; + // let normal_dir = match axis { + // 0 => [sign, 0.0, 0.0], + // 1 => [0.0, sign, 0.0], + // 2 => [0.0, 0.0, sign], + // _ => unreachable!("Axis must be 0, 1, or 2"), + // }; + + // let indices_cell = self.cell_points(cell1_id); + + // // Cell edges + // let r0 = self.get_cell_edge(0, indices_cell[0]); + // let r1 = self.get_cell_edge(0, indices_cell[0] + 1); + // let theta0 = self.get_cell_edge(1, indices_cell[1]); + // let theta1 = self.get_cell_edge(1, indices_cell[1] + 1); + // let z0 = self.get_cell_edge(2, indices_cell[2]); + // let z1 = self.get_cell_edge(2, indices_cell[2] + 1); + // let pi = std::f64::consts::PI; + // let normalize = |a: f64| -> f64 { + // let mut x = a % (2.0 * pi); + // if x > pi { + // x -= 2.0 * pi; + // } + // if x < -pi { + // x += 2.0 * pi; + // } + // x + // }; + + // // Centers + // let r_center = 0.5 * (r0 + r1); + + // let z_center = 0.5 * (z0 + z1); + // // let theta_center = 0.5 * (theta0 + theta1); + // let theta_center = { + // let mut dtheta = theta1 - theta0; + // if dtheta > pi { + // dtheta -= 2.0 * pi; + // } + // if dtheta < -pi { + // dtheta += 2.0 * pi; + // } + // normalize(theta0 + 0.5 * dtheta) + // }; + + // // Origin of the plane (on interface) + // let (r, theta, z) = match axis { + // 0 => (if sign < 0.0 { r0 } else { r1 }, theta_center, z_center), + // 1 => (r_center, if sign < 0.0 { theta0 } else { theta1 }, z_center), + // 2 => (r_center, theta_center, if sign < 0.0 { z0 } else { z1 }), + // _ => unreachable!(), + // }; + + // let (extent_u, extent_v) = match axis { + // 0 => ([theta0, theta1], [z0, z1]), + // 1 => ([r0, r1], [z0, z1]), + // 2 => ([r0, r1], [theta0, theta1]), + // _ => unreachable!(), + // }; + + // if axis == 0 { + // let bounded_plane = get_tangent_plane_at_r(axis, r, theta, z, extent_u, extent_v); + // (bounded_plane, axis) + // } else { + // let cyl_normal = CylindricalVec3(normal_dir, theta); + // let normal_cartesian = cyl_normal.to_cartesian_vec(); + // let origin = CylindricalCoordinates([r, theta, z]).into(); + // let bounded_plane = BoundedPlane { + // normal: normal_cartesian, + // origin, + // extent_u, + // extent_v, + // axis, + // }; + // (bounded_plane, axis) + // } + // } + fn get_interface_plane(&self, cell1_id: usize, cell2_id: usize) -> (BoundedPlane, usize) { let neighbors = self.are_cell_neighbor(cell1_id, cell2_id); let axis = neighbors @@ -435,13 +541,34 @@ impl CompartmentMeshManip for MeshCylindrical { let theta1 = self.get_cell_edge(1, indices_cell[1] + 1); let z0 = self.get_cell_edge(2, indices_cell[2]); let z1 = self.get_cell_edge(2, indices_cell[2] + 1); + let pi = std::f64::consts::PI; + let normalize = |a: f64| -> f64 { + let mut x = a % (2.0 * pi); + if x > pi { + x -= 2.0 * pi; + } + if x < -pi { + x += 2.0 * pi; + } + x + }; // Centers let r_center = 0.5 * (r0 + r1); - let theta_center = 0.5 * (theta0 + theta1); + let z_center = 0.5 * (z0 + z1); + // let theta_center = 0.5 * (theta0 + theta1); + let theta_center = { + let mut dtheta = theta1 - theta0; + if dtheta > pi { + dtheta -= 2.0 * pi; + } + if dtheta < -pi { + dtheta += 2.0 * pi; + } + normalize(theta0 + 0.5 * dtheta) + }; - // Origin of the plane (on interface) let (r, theta, z) = match axis { 0 => (if sign < 0.0 { r0 } else { r1 }, theta_center, z_center), 1 => (r_center, if sign < 0.0 { theta0 } else { theta1 }, z_center), @@ -450,18 +577,24 @@ impl CompartmentMeshManip for MeshCylindrical { }; let (extent_u, extent_v) = match axis { - 0 => ([theta0, theta1], [z0, z1]), // u = theta, v = z - 1 => ([r0, r1], [z0, z1]), // u = r, v = z - 2 => ([r0, r1], [theta0, theta1]), // u = r, v = theta + 0 => ([theta0, theta1], [z0, z1]), + 1 => ([r0, r1], [z0, z1]), + 2 => ([r0, r1], [theta0, theta1]), _ => unreachable!(), }; if axis == 0 { - // axe r -> plan tangent au cylindre - let bounded_plane = get_tangent_plane_at_r(axis, r, theta, z, extent_u, extent_v); + let normal_cartesian = CartesianVec3([theta.cos(), theta.sin(), 0.0]); + let origin = CartesianCoordinates([r * theta.cos(), r * theta.sin(), z]); + let bounded_plane = BoundedPlane { + normal: normal_cartesian, + origin, + extent_u, + extent_v, + axis, + }; (bounded_plane, axis) } else { - // pour axis 1 et 2 on garde ta méthode normale let cyl_normal = CylindricalVec3(normal_dir, theta); let normal_cartesian = cyl_normal.to_cartesian_vec(); let origin = CylindricalCoordinates([r, theta, z]).into(); diff --git a/cmtool-core/src/model/interfaces.rs b/cmtool-core/src/model/interfaces.rs index 766eb791..0abb1e8b 100644 --- a/cmtool-core/src/model/interfaces.rs +++ b/cmtool-core/src/model/interfaces.rs @@ -140,38 +140,6 @@ impl AInterfacesInfo { global_id_from_interface } - //fn count_interfaces_second_pass( - //&mut self, - //geometry: &CMGeometry, - //interfaces_id_from_cells: &[usize], - //) -> Vec> { - //let mut tmp_element_counter = vec![0; self.n_facet.len()]; - //let mut global_id_from_interface: Vec> = vec![Vec::new(); self.n_facet.len()]; - //for (element_id, n_element) in global_id_from_interface.iter_mut().zip(self.n_facet.iter()) - //{ - //*element_id = vec![0; *n_element]; - //} - //let grid = geometry.get_grid().unwrap(); - //let n_zones = geometry.n_zone(); - //let functor = |vol_element_global_id: usize, - //interface_cid_0: usize, - //interface_cid_k: usize, - //k_vertex: usize| { - //if k_vertex >= 1 - //&& grid.are_cell_neighbor(interface_cid_0, interface_cid_k)!= NeighborDirection::NotNeighbors - //{ - //let interface_global_id = - //interfaces_id_from_cells[interface_cid_0 * n_zones + interface_cid_k]; - //let k_element = tmp_element_counter[interface_global_id]; - //tmp_element_counter[interface_global_id] += 1; - //global_id_from_interface[interface_global_id][k_element] = vol_element_global_id; - //} - //}; - //geometry.interface_iterator(functor); - // - //global_id_from_interface - //} - fn fill_area(&mut self, geometry: &CMGeometry, planes: &[BoundedPlane]) { //This is almost the same algorithm as fill for c_info struct (to compute volume of velem) for (i, n) in self.n_facet.iter().enumerate() { @@ -193,9 +161,7 @@ impl AInterfacesInfo { let area = compute_intersection_area(&local_vertices, elem_type, plane) .expect("Area between element"); - if area == 0. { - println!("{} {}", interface_id, i_facet); - } + self.area[interface_id][i_facet] = area; } } diff --git a/cmtool-core/src/model/mod.rs b/cmtool-core/src/model/mod.rs index f390d38c..bc6de354 100644 --- a/cmtool-core/src/model/mod.rs +++ b/cmtool-core/src/model/mod.rs @@ -98,18 +98,27 @@ impl CMModel { for (global_id, area) in curent_inteface_element.iter().zip(current_interface_area) { let vector_value = CartesianVec3(vector.get_slice_xyz(*global_id).to_owned()); - let coords = if self.geometry.mesh_type == MeshType::Cylindrical { - let CartesianCoordinates(centroid) = - self.geometry.volume_elements.xyz[*global_id]; - - let CylindricalCoordinates(centroid) = CartesianCoordinates(centroid).into(); - - vector_value.to_cylindrical_vec(centroid[1]).0 - } else { - vector_value.0 + let coords = match self.geometry.mesh_type { + MeshType::Cylindrical => { + let CartesianCoordinates(centroid) = + self.geometry.volume_elements.xyz[*global_id]; + + let CylindricalCoordinates(centroid) = + CartesianCoordinates(centroid).into(); + + let cyl_vec = vector_value.to_cylindrical_vec(centroid[1]).0; + + let r = (centroid[0].powi(2) + centroid[1].powi(2)).sqrt(); + match axis { + 1 => [cyl_vec[0], cyl_vec[1] * r, cyl_vec[2]], + _ => cyl_vec, + } + } + _ => vector_value.0, }; let f = coords[axis] * area; + if f > 0. { flow.source_flow += f } else if f < 0. { diff --git a/cmtool-core/src/utils/area.rs b/cmtool-core/src/utils/area.rs index 0483d897..ec24137a 100644 --- a/cmtool-core/src/utils/area.rs +++ b/cmtool-core/src/utils/area.rs @@ -76,6 +76,102 @@ fn sort_points_ccw_3d(points: &[[f64; 3]], normal: &CartesianVec3) -> Vec<[f64; // area.abs() * 0.5 // } +fn polygon_area_3d(points: &[Coords3], normal: &CartesianVec3) -> f64 { + let n = normal.normalized(); + + let mut area_vec = CartesianVec3([0.0, 0.0, 0.0]); + let n_pts = points.len(); + + for i in 0..n_pts { + let p1 = CartesianVec3(points[i]); + let p2 = CartesianVec3(points[(i + 1) % n_pts]); + + let cross = p1.cross(&p2); + + area_vec = area_vec.add(&cross); + } + 0.5 * (area_vec.dot(&n)).abs() +} + +// fn tetra_area(vertices: [CartesianCoordinates; 4], plane: &BoundedPlane) -> f64 { +// let mut intersection_points = vec![]; + +// let BoundedPlane { +// normal, +// origin: point, +// .. +// } = plane; + +// let d = -normal.dot(&CartesianVec3::from_point_origin(*point)); // plane offset +// let distances: Vec = vertices +// .iter() +// .map(|coords| CartesianVec3::from_point_origin(*coords)) +// .map(|v| normal.dot(&v) + d) +// .collect(); + +// let mut points_on_plane = vec![]; +// let edge_len = (0..4) +// .flat_map(|i| (i + 1..4).map(move |j| (i, j))) +// .map(|(i, j)| { +// let e = [ +// vertices[i].0[0] - vertices[j].0[0], +// vertices[i].0[1] - vertices[j].0[1], +// vertices[i].0[2] - vertices[j].0[2], +// ]; +// (e[0] * e[0] + e[1] * e[1] + e[2] * e[2]).sqrt() +// }) +// .fold(0.0_f64, f64::max); +// let tol = 1e-4 * edge_len.max(1.0); + +// for (i, dist) in distances.iter().enumerate() { +// if dist.abs() < tol { +// points_on_plane.push(vertices[i].0); +// } +// } + +// for i in 0..4 { +// for j in (i + 1)..4 { +// let d1 = distances[i]; +// let d2 = distances[j]; +// if d1.abs() < tol || d2.abs() < tol { +// continue; +// } +// if d1 * d2 < 0.0 { +// let t = d1 / (d1 - d2); +// let p1 = &vertices[i].0; +// let p2 = &vertices[j].0; +// let intersection = [ +// p1[0] + t * (p2[0] - p1[0]), +// p1[1] + t * (p2[1] - p1[1]), +// p1[2] + t * (p2[2] - p1[2]), +// ]; +// intersection_points.push(intersection); +// } +// } +// } +// intersection_points.extend(points_on_plane.iter().cloned()); + +// if intersection_points.len() < 3 { +// return 0.0; +// } + +// let filtered: Vec<_> = intersection_points +// .iter() +// .cloned() +// .filter(|p| plane.is_point_inside(CartesianCoordinates(*p))) +// .collect(); + +// if filtered.len() < 3 { +// return 0.0; +// } + +// let sorted = sort_points_ccw_3d(&intersection_points, normal); + +// //Compute area using shoelace formula +// // return polygon_area_2d(&sorted); +// polygon_area_3d(&sorted, normal) +// } + fn tetra_area(vertices: [CartesianCoordinates; 4], plane: &BoundedPlane) -> f64 { let mut intersection_points = vec![]; @@ -93,9 +189,21 @@ fn tetra_area(vertices: [CartesianCoordinates; 4], plane: &BoundedPlane) -> f64 .collect(); let mut points_on_plane = vec![]; - const TOL: f64 = 1e-1; + let edge_len = (0..4) + .flat_map(|i| (i + 1..4).map(move |j| (i, j))) + .map(|(i, j)| { + let e = [ + vertices[i].0[0] - vertices[j].0[0], + vertices[i].0[1] - vertices[j].0[1], + vertices[i].0[2] - vertices[j].0[2], + ]; + (e[0] * e[0] + e[1] * e[1] + e[2] * e[2]).sqrt() + }) + .fold(0.0_f64, f64::max); + let tol = 1e-10 * edge_len.max(1.0); + for (i, dist) in distances.iter().enumerate() { - if dist.abs() < TOL { + if dist.abs() < tol { points_on_plane.push(vertices[i].0); } } @@ -104,9 +212,11 @@ fn tetra_area(vertices: [CartesianCoordinates; 4], plane: &BoundedPlane) -> f64 for j in (i + 1)..4 { let d1 = distances[i]; let d2 = distances[j]; - + if d1.abs() < tol || d2.abs() < tol { + continue; + } if d1 * d2 < 0.0 { - let t = d1 / (d1 - d2); + let t = d1.abs() / (d1.abs() + d2.abs()); // Correction ici: formule plus stable let p1 = &vertices[i].0; let p2 = &vertices[j].0; let intersection = [ @@ -118,56 +228,26 @@ fn tetra_area(vertices: [CartesianCoordinates; 4], plane: &BoundedPlane) -> f64 } } } + intersection_points.extend(points_on_plane.iter().cloned()); if intersection_points.len() < 3 { - if points_on_plane.len() >= 3 { - let filtered: Vec<_> = points_on_plane - .iter() - .cloned() - .filter(|p| plane.is_point_inside(CartesianCoordinates(*p))) - .collect(); - - if filtered.len() >= 3 { - let sorted = sort_points_ccw_3d(&filtered, normal); - return polygon_area_3d(&sorted, normal); - } else { - return 0.0; - } - } else { - return 0.0; - } + return 0.0; } - //Project points to 2D plane - // let projected = project_points_to_plane_2d(&intersection_points, normal); + let filtered: Vec<_> = intersection_points + .iter() + .cloned() + .filter(|p| plane.is_point_inside(CartesianCoordinates(*p))) + .collect(); - // //Sort points counterclockwise - // let sorted = sort_polygon_ccw(&projected); + if filtered.len() < 3 { + return 0.0; + } let sorted = sort_points_ccw_3d(&intersection_points, normal); - - //Compute area using shoelace formula - // return polygon_area_2d(&sorted); polygon_area_3d(&sorted, normal) } -fn polygon_area_3d(points: &[Coords3], normal: &CartesianVec3) -> f64 { - let n = normal.normalized(); - - let mut area_vec = CartesianVec3([0.0, 0.0, 0.0]); - let n_pts = points.len(); - - for i in 0..n_pts { - let p1 = CartesianVec3(points[i]); - let p2 = CartesianVec3(points[(i + 1) % n_pts]); - - let cross = p1.cross(&p2); - - area_vec = area_vec.add(&cross); - } - 0.5 * (area_vec.dot(&n)).abs() -} - pub fn compute_intersection_area( local_vertices: &[CartesianCoordinates], elem_type: VolumeElementTypes, diff --git a/examples/python/mixing_simple.py b/examples/python/mixing_simple.py index 13d98e8a..0637c1ea 100644 --- a/examples/python/mixing_simple.py +++ b/examples/python/mixing_simple.py @@ -134,7 +134,7 @@ def check_mixing(fmt, final_time: float): if __name__ == "__main__": - final_time = 50 + final_time = 1000 root = os.environ["EXAMPLE_ROOT"] # Let CMTool read and load the full case automatically, ready to iterate From c2822d260d3bb5a08653fc27071aa91cb9edf584 Mon Sep 17 00:00:00 2001 From: bcasale Date: Wed, 18 Mar 2026 09:26:59 +0100 Subject: [PATCH 33/44] fix(cm): wip flow calculation --- cmtool-core/src/coordinates/vec3.rs | 2 +- cmtool-core/src/lib.rs | 4 +++ cmtool-core/src/model/geometry.rs | 54 +++++++++++++++++++++++++---- cmtool-core/src/model/interfaces.rs | 53 +++++++++++++++++++++------- cmtool-core/src/model/mod.rs | 14 ++++++-- cmtool/src/main.rs | 9 ----- 6 files changed, 105 insertions(+), 31 deletions(-) diff --git a/cmtool-core/src/coordinates/vec3.rs b/cmtool-core/src/coordinates/vec3.rs index 33f1c350..a87aea38 100644 --- a/cmtool-core/src/coordinates/vec3.rs +++ b/cmtool-core/src/coordinates/vec3.rs @@ -5,7 +5,7 @@ use crate::coordinates::{CartesianCoordinates, Coords3}; #[derive(Clone, Copy, Debug)] pub struct CartesianVec3(pub Coords3); -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub struct CylindricalVec3(pub Coords3, pub f64); pub trait Vec3 { diff --git a/cmtool-core/src/lib.rs b/cmtool-core/src/lib.rs index 1923a4d3..c8d46efe 100644 --- a/cmtool-core/src/lib.rs +++ b/cmtool-core/src/lib.rs @@ -43,6 +43,10 @@ pub struct CMHandle { } impl CMHandle { + pub fn grid(&self) -> &dyn crate::grid::CompartmentMesh { + self.model.grid() + } + pub fn init( n_div: [usize; 3], root: &str, diff --git a/cmtool-core/src/model/geometry.rs b/cmtool-core/src/model/geometry.rs index 2fb76f3a..f3ef3c31 100644 --- a/cmtool-core/src/model/geometry.rs +++ b/cmtool-core/src/model/geometry.rs @@ -200,22 +200,62 @@ impl CMGeometry { let mut count = CountVolumeElement::new(self.n_zone()); let grid = self.grid.as_ref().unwrap(); - for (_gid, interface_cid_0, interface_cid_k, k_vertex) in self.interface_iter() { - count.incr_compartment(interface_cid_k); + // for (_gid, interface_cid_0, interface_cid_k, k_vertex) in self.interface_iter() { + // count.incr_compartment(interface_cid_k); - if k_vertex >= 1 { - let neighbors = grid.are_cell_neighbor(interface_cid_0, interface_cid_k); + // if k_vertex >= 1 { + // let neighbors = grid.are_cell_neighbor(interface_cid_0, interface_cid_k); - if neighbors != NeighborDirection::NotNeighbors { - let (id1, id2) = neighbors.ordered_pair(interface_cid_0, interface_cid_k); + // if neighbors != NeighborDirection::NotNeighbors { + // let (id1, id2) = neighbors.ordered_pair(interface_cid_0, interface_cid_k); - count.incr_interface(id1, id2); + // count.incr_interface(id1, id2); + // } + // } + // } + // + for (vol_element_global_id, _, interface_cid_k, k_vertex) in self.interface_iter() { + count.incr_compartment(interface_cid_k); + if k_vertex >= 1 { + for i in 0..k_vertex { + let cid_i = self + .volume_elements + .get_list_compartment_id(vol_element_global_id, i); + let neighbors = grid.are_cell_neighbor(cid_i, interface_cid_k); + if neighbors != NeighborDirection::NotNeighbors { + let (id1, id2) = neighbors.ordered_pair(cid_i, interface_cid_k); + count.incr_interface(id1, id2); + } } } } count } + // pub fn get_count_volume_element_first_pass(&self) -> CountVolumeElement { + // let mut count = CountVolumeElement::new(self.n_zone()); + + // let grid = self.grid.as_ref().unwrap(); + + // let mut seen_interfaces = std::collections::HashSet::new(); + + // for (_, interface_cid_0, interface_cid_k, _) in self.interface_iter() { + // count.incr_compartment(interface_cid_k); + // count.incr_compartment(interface_cid_0); + // if interface_cid_0 != interface_cid_k { + // let neighbors = grid.are_cell_neighbor(interface_cid_0, interface_cid_k); + // if neighbors != NeighborDirection::NotNeighbors { + // let (id1, id2) = neighbors.ordered_pair(interface_cid_0, interface_cid_k); + // if !seen_interfaces.contains(&(id1, id2)) { + // seen_interfaces.insert((id1, id2)); + // count.incr_interface(id1, id2); + // } + // } + // } + // } + + // count + // } pub fn init( n_div: [usize; 3], diff --git a/cmtool-core/src/model/interfaces.rs b/cmtool-core/src/model/interfaces.rs index 0abb1e8b..bfb539a9 100644 --- a/cmtool-core/src/model/interfaces.rs +++ b/cmtool-core/src/model/interfaces.rs @@ -23,6 +23,7 @@ pub struct AInterfacesInfo { pub area: Vec>, pub normal_axis: Vec, pub global_id_from_interface: Vec>, + pub interface_theta: Vec, // pub plane_coordinates: Vec, // pub planes: Vec, } @@ -37,6 +38,7 @@ impl AInterfacesInfo { area: vec![Default::default(); n_interfaces], normal_axis: vec![Default::default(); n_interfaces], global_id_from_interface: vec![Default::default(); n_interfaces], + interface_theta: vec![Default::default(); n_interfaces], // plane_coordinates: vec![0.; n_interfaces * 3 * 2], //Extent geometry // planes: Vec::new(), } @@ -99,6 +101,10 @@ impl AInterfacesInfo { interfaces_id_from_cells[target_id * n_zones + source_id] = interface_id; let (plane, direction_neighbors) = grid.get_interface_plane(source_id, target_id); + + let origin = plane.origin.0; + let theta = origin[1].atan2(origin[0]); + self.interface_theta[interface_id] = theta; planes.push(plane); self.normal_axis[interface_id] = direction_neighbors; } @@ -122,18 +128,36 @@ impl AInterfacesInfo { let grid = geometry.get_grid().unwrap(); let n_zones = geometry.n_zone(); - for (vol_element_global_id, interface_cid_0, interface_cid_k, k_vertex) in - geometry.interface_iter() - { - if k_vertex >= 1 - && grid.are_cell_neighbor(interface_cid_0, interface_cid_k) - != NeighborDirection::NotNeighbors - { - let interface_global_id = - interfaces_id_from_cells[interface_cid_0 * n_zones + interface_cid_k]; - let k_element = tmp_element_counter[interface_global_id]; - tmp_element_counter[interface_global_id] += 1; - global_id_from_interface[interface_global_id][k_element] = vol_element_global_id; + // for (vol_element_global_id, interface_cid_0, interface_cid_k, k_vertex) in + // geometry.interface_iter() + // { + // if k_vertex >= 1 + // && grid.are_cell_neighbor(interface_cid_0, interface_cid_k) + // != NeighborDirection::NotNeighbors + // { + // let interface_global_id = + // interfaces_id_from_cells[interface_cid_0 * n_zones + interface_cid_k]; + // let k_element = tmp_element_counter[interface_global_id]; + // tmp_element_counter[interface_global_id] += 1; + // global_id_from_interface[interface_global_id][k_element] = vol_element_global_id; + // } + // } + for (vol_element_global_id, _, interface_cid_k, k_vertex) in geometry.interface_iter() { + if k_vertex >= 1 { + for i in 0..k_vertex { + let cid_i = geometry + .volume_elements + .get_list_compartment_id(vol_element_global_id, i); + let neighbors = grid.are_cell_neighbor(cid_i, interface_cid_k); + if neighbors != NeighborDirection::NotNeighbors { + let interface_global_id = + interfaces_id_from_cells[cid_i * n_zones + interface_cid_k]; + let k_element = tmp_element_counter[interface_global_id]; + tmp_element_counter[interface_global_id] += 1; + global_id_from_interface[interface_global_id][k_element] = + vol_element_global_id; + } + } } } @@ -159,6 +183,11 @@ impl AInterfacesInfo { geometry.fill_vertices(volume_element_global_id, n_vertex, &mut local_vertices); + // let area = compute_intersection_area(&local_vertices, elem_type, plane) + // .expect("Area between element"); + // let area = area * plane.normal.0[plane.axis].signum(); + // self.area[interface_id][i_facet] = area; + // let area = compute_intersection_area(&local_vertices, elem_type, plane) .expect("Area between element"); diff --git a/cmtool-core/src/model/mod.rs b/cmtool-core/src/model/mod.rs index bc6de354..5efe623a 100644 --- a/cmtool-core/src/model/mod.rs +++ b/cmtool-core/src/model/mod.rs @@ -32,6 +32,10 @@ pub struct CMModel { pub use geometry::CMGeometry; impl CMModel { + pub fn grid(&self) -> &dyn crate::grid::CompartmentMesh { + self.geometry.get_grid().unwrap() + } + pub fn init(geometry: Arc) -> Self { println!("Init model with {} compartment", geometry.n_zone()); let volume_element_count = geometry.get_count_volume_element_first_pass(); @@ -52,7 +56,11 @@ impl CMModel { let n_max_interface = geometry.get_grid().as_ref().unwrap().n_maximum_interface(); if interfaces.n_interfaces() >= n_max_interface { - unimplemented!("should have intefaces < n_maximum_interface") + unimplemented!( + "should have intefaces < n_maximum_interface {} {}", + interfaces.n_interfaces(), + n_max_interface + ) } // if interfaces.n_facet.len() != n_max_interface { @@ -106,7 +114,9 @@ impl CMModel { let CylindricalCoordinates(centroid) = CartesianCoordinates(centroid).into(); - let cyl_vec = vector_value.to_cylindrical_vec(centroid[1]).0; + let cyl_vec = vector_value + .to_cylindrical_vec(self.interfaces.interface_theta[i_interface]) + .0; let r = (centroid[0].powi(2) + centroid[1].powi(2)).sqrt(); match axis { diff --git a/cmtool/src/main.rs b/cmtool/src/main.rs index 3b0ca9ae..9ce0aee3 100644 --- a/cmtool/src/main.rs +++ b/cmtool/src/main.rs @@ -37,15 +37,6 @@ fn auto_main(common: CommonArgs, autoargs: AutoArgs) -> Result<(), CmtoolError> .and_then(|s| s.to_str()) .unwrap(); // Converts OsStr to &str - // let mut f = cmtool::check_flows( - // &RawDataFlux::read_raw("./out/cuve_sldmsh_initmrf/velocity.raw").unwrap(), - // ) - // .unwrap(); - - // println!("{}", f); - - // return Ok(()); - let root_dir = out_or_default(common.out); let case = cmtool_core::ensight_gold::case::Case::read(&autoargs.case_path)?; From 959e0ed693642a78efb8d131767e19ee37776599 Mon Sep 17 00:00:00 2001 From: bcasale Date: Wed, 18 Mar 2026 09:28:50 +0100 Subject: [PATCH 34/44] feat: try impl divergence free method --- Cargo.lock | 2 ++ cmtool/Cargo.toml | 3 +- cmtool/src/main.rs | 16 ++++++++- cmtool/src/sanitizer.rs | 80 +++++++++++++++++++++++++++++++++++++++-- 4 files changed, 96 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bac47e5f..e23c8ec1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -201,6 +201,8 @@ dependencies = [ "cmtool-assemble", "cmtool-core", "cmtool-data", + "nalgebra", + "nalgebra-sparse", "thiserror", ] diff --git a/cmtool/Cargo.toml b/cmtool/Cargo.toml index 25037e28..3c6a3691 100644 --- a/cmtool/Cargo.toml +++ b/cmtool/Cargo.toml @@ -12,7 +12,8 @@ cmtool-core.workspace = true cmtool-data.workspace = true cmtool-assemble.workspace = true thiserror.workspace = true - +nalgebra.workspace = true +nalgebra-sparse.workspace=true [features] use_vtk = ["cmtool-core/use_vtk"] diff --git a/cmtool/src/main.rs b/cmtool/src/main.rs index 9ce0aee3..10e7ff28 100644 --- a/cmtool/src/main.rs +++ b/cmtool/src/main.rs @@ -56,17 +56,31 @@ fn auto_main(common: CommonArgs, autoargs: AutoArgs) -> Result<(), CmtoolError> .map_err(CmtoolError::Core)?; handle.dump_real_volume(format!("{}/{}/vofL", root_dir, stem))?; + handle.dump_real_volume(format!("{}/{}/vtot", root_dir, stem))?; + + handle.dump_vector_from_scalar( + format!("{}/{}/flowL", root_dir, stem), + "/tmp/sanofi/inputs/RESULTS.scl1", + "/tmp/sanofi/inputs/RESULTS.scl2", + "/tmp/sanofi/inputs/RESULTS.scl3", + )?; #[cfg(feature = "use_vtk")] handle.write_vtk(format!("{}/{}/cma_case.vtu", root_dir, stem)); let f = cmtool::check_flows( + handle.grid(), &RawDataFlux::read_raw("./out/cuve_sldmsh_initmrf/velocity.raw").unwrap(), ) .unwrap(); println!("{}", f); // std::fs::write("/tmp/checks.csv", f); - + // + // let f = cmtool::divergence_free( + // &RawDataFlux::read_raw("./out/cuve_sldmsh_initmrf/velocity.raw").unwrap(), + // ); + // f.write_raw("./out/cuve_sldmsh_initmrf/velocity2.raw") + // .unwrap(); // let mut case = cmtool_data::CMCase::new( // [common.n_i as u32, common.n_j as u32, common.n_k as u32], // 0., diff --git a/cmtool/src/sanitizer.rs b/cmtool/src/sanitizer.rs index 38de668a..5a889de9 100644 --- a/cmtool/src/sanitizer.rs +++ b/cmtool/src/sanitizer.rs @@ -12,12 +12,24 @@ fn smape(a: f64, b: f64) -> f64 { (a - b).abs() / (a.abs() + b.abs()) } -pub fn check_flows(raw_flows: &cmtool_data::RawDataFlux) -> Option { +pub fn check_flows( + _grid: &dyn cmtool_core::grid::CompartmentMeshManip, + raw_flows: &cmtool_data::RawDataFlux, +) -> Option { let mut mass_balance: Vec<(f64, f64)> = vec![(0.0, 0.0); raw_flows.header.n_zone as usize]; + // let boundary = grid.get_boundary(); + for flow in raw_flows.fluxes.iter() { + // mass_balance[flow.id_target as usize].0 += flow.flux_source_target; + // mass_balance[flow.id_source as usize].1 += flow.flux_source_target; + // + // mass_balance[flow.id_target as usize].0 += flow.flux_source_target; mass_balance[flow.id_source as usize].1 += flow.flux_source_target; + + mass_balance[flow.id_source as usize].0 += flow.flux_target_source; + mass_balance[flow.id_target as usize].1 += flow.flux_target_source; } let mut zone_relative_errors = Vec::with_capacity(raw_flows.header.n_zone as usize); @@ -27,9 +39,15 @@ pub fn check_flows(raw_flows: &cmtool_data::RawDataFlux) -> Option { let mut total_inflow = 0.0; let mut total_outflow = 0.0; let mut f = String::new(); + let mut n_zone = 0; writeln!(&mut f, "zone_id,int,out,relative_error_percent").unwrap(); for (i, (inflow, outflow)) in mass_balance.iter().enumerate() { + // let is_boundary = boundary.iter().find(|&&ci| ci == i); + // if is_boundary.is_some() { + // continue; + // } + n_zone += 1; let relative_error = smape(*inflow, *outflow); zone_relative_errors.push(relative_error); @@ -39,18 +57,29 @@ pub fn check_flows(raw_flows: &cmtool_data::RawDataFlux) -> Option { total_inflow += inflow; total_outflow += outflow; + // writeln!( + // &mut f, + // "{},{},{},{},{}", + // i + 1, + // inflow, + // outflow, + // relative_error * 100.0, + // is_boundary.is_some() + // ) + // .unwrap(); writeln!( &mut f, "{},{},{},{}", i + 1, inflow, outflow, - relative_error * 100.0 + relative_error * 100.0, ) .unwrap(); } writeln!(&mut f).unwrap(); - let mean_relative_error = total_relative_error / raw_flows.header.n_zone as f64; + + let mean_relative_error = total_relative_error / n_zone as f64; writeln!(&mut f, "metric,value").unwrap(); writeln!(&mut f, "total_inflow,{:.8}", total_inflow).unwrap(); @@ -76,3 +105,48 @@ pub fn check_flows(raw_flows: &cmtool_data::RawDataFlux) -> Option { Some(f) } + +pub fn divergence_free(raw_flows: &cmtool_data::RawDataFlux) -> cmtool_data::RawDataFlux { + use nalgebra::{DMatrix, DVector}; + let n_zone = raw_flows.header.n_zone as usize; + let n_flux = raw_flows.fluxes.len() * 2; + let mut new_flows = raw_flows.clone(); + + let mut f_vec = DVector::from_element(n_flux, 0.0); + for (i, flow) in new_flows.fluxes.iter().enumerate() { + f_vec[i * 2] = flow.flux_source_target; + f_vec[i * 2 + 1] = flow.flux_target_source; + } + + let mut a_mat = DMatrix::zeros(n_zone, n_flux); + for (k, flow) in new_flows.fluxes.iter().enumerate() { + let s = flow.id_source as usize; + let t = flow.id_target as usize; + + a_mat[(s, k * 2)] = 1.0; + a_mat[(t, k * 2)] = -1.0; + + a_mat[(t, k * 2 + 1)] = 1.0; + a_mat[(s, k * 2 + 1)] = -1.0; + } + + let div = &a_mat * &f_vec; + + let a_at = a_mat.transpose(); + let aat_inv = match (a_mat.clone() * a_at.clone()).try_inverse() { + Some(inv) => inv, + None => panic!("Cannot invert A*A^T"), + }; + let delta_f = a_at * (aat_inv * (-div)); + + for (i, flow) in new_flows.fluxes.iter_mut().enumerate() { + flow.flux_source_target += delta_f[i * 2]; + flow.flux_target_source += delta_f[i * 2 + 1]; + + if flow.flux_source_target < 0.0 || flow.flux_target_source < 0.0 { + panic!("Negative flux encountered"); + } + } + + new_flows +} From 99a588f86fe42da5b199daf2be79b68894dffc13 Mon Sep 17 00:00:00 2001 From: bcasale Date: Wed, 18 Mar 2026 09:29:22 +0100 Subject: [PATCH 35/44] feat(example): add example manip CM --- cmtool-python/src/rd.rs | 19 ++++++++++++----- examples/python/mixing_simple.py | 3 ++- examples/python/simple_cm.py | 35 ++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 examples/python/simple_cm.py diff --git a/cmtool-python/src/rd.rs b/cmtool-python/src/rd.rs index ae9998ca..c27b823b 100644 --- a/cmtool-python/src/rd.rs +++ b/cmtool-python/src/rd.rs @@ -23,6 +23,14 @@ impl RawDataScalarWrapper { let array = numpy::ndarray::Array1::from(values); PyArray1::from_owned_array(py, array).unbind() } + + pub fn write(&self, path: &str) -> PyResult<()> { + if self.0.write_raw(path).is_ok() { + Ok(()) + } else { + Err(PyValueError::new_err("Scalar not found")) + } + } } #[pyfunction] @@ -51,11 +59,12 @@ pub fn scalar_from_data<'py>( match x.as_slice() { Ok(slice) => { - if slice.len() != 1 { - return Err(PyValueError::new_err( - "Input array must contain exactly one element.", - )); - } + //TODO Why this condition has been used ? + // if slice.len() != 1 { + // return Err(PyValueError::new_err( + // "Input array must contain exactly one element.", + // )); + // } let scalar = RawDataScalar::from(slice); Ok(RawDataScalarWrapper(scalar)) } diff --git a/examples/python/mixing_simple.py b/examples/python/mixing_simple.py index 0637c1ea..e15c2444 100644 --- a/examples/python/mixing_simple.py +++ b/examples/python/mixing_simple.py @@ -58,6 +58,7 @@ def wrap(t: float, x: np.ndarray) -> np.ndarray: liquid_state = it.liquid # Get the transition matrix transition = pycmtool.get_sparse_transition_matrix(liquid_state) + vol = liquid_state.volumes # Volume of each compartment _mass = x.reshape((n_species, n_c)) # Reshape to (N_SPECIES, n_compartments) C = _mass / vol # Concentration: mass / volume @@ -134,7 +135,7 @@ def check_mixing(fmt, final_time: float): if __name__ == "__main__": - final_time = 1000 + final_time = 100 root = os.environ["EXAMPLE_ROOT"] # Let CMTool read and load the full case automatically, ready to iterate diff --git a/examples/python/simple_cm.py b/examples/python/simple_cm.py new file mode 100644 index 00000000..ffaec3c1 --- /dev/null +++ b/examples/python/simple_cm.py @@ -0,0 +1,35 @@ +import numpy as np +import pycmtool + + +def generate_liq_volume(): + total_volume = pycmtool.data.read_rawscalar("./out/RESULTS/vtot.raw") + vgas = pycmtool.data.read_rawscalar("./out/RESULTS/gas_vof.raw") + v_liq = np.array(total_volume.data - vgas.data) + sc = pycmtool.data.scalar_from_data(v_liq) + sc.write("./out/RESULTS/liq_vof.raw") + + +if __name__ == "__main__": + generate_liq_volume() + # Check volumes + total_volume = pycmtool.data.read_rawscalar("./out/RESULTS/vtot.raw") + vgas = pycmtool.data.read_rawscalar("./out/RESULTS/gas_vof.raw") + vliq = pycmtool.data.read_rawscalar("./out/RESULTS/liq_vof.raw") + + geometric_volume = np.pi * np.power(5.78824, 2) / 4 * 10.4736 + + vtot = np.sum(vliq.data) + np.sum(vgas.data) + print( + geometric_volume, + np.sum(vliq.data), + np.sum(vgas.data), + vtot, + np.sum(total_volume.data), + ) + assert np.abs(vtot - np.sum(total_volume.data)) < 1e-10 + + vL = pycmtool.data.read_rawscalar("/tmp/sanofi/vofL.raw") + vG = pycmtool.data.read_rawscalar("/tmp/sanofi/vofG.raw") + vtot = np.sum(vL.data) + np.sum(vG.data) + print(vtot, np.sum(vL.data), np.sum(vG.data)) From 75334691a7809f924d746c299ac941a9b1d56990 Mon Sep 17 00:00:00 2001 From: bcasale Date: Wed, 18 Mar 2026 11:04:34 +0100 Subject: [PATCH 36/44] fix(xml): hot fix because of xsd-parser update --- Cargo.lock | 100 +++++++++--------- Cargo.toml | 2 +- cmtool-assemble/build.rs | 58 +++++----- cmtool-assemble/datamodel/connections.xsd | 42 +++++--- cmtool-assemble/datamodel/main.xsd | 33 +++--- cmtool-assemble/datamodel/reactors.xsd | 12 +-- cmtool-assemble/datamodel/units.xsd | 15 ++- cmtool-assemble/src/map_generation.rs | 2 +- .../src/parser/generated_domain.rs | 11 +- cmtool-assemble/src/parser/reactors.rs | 4 +- flake.nix | 12 +-- 11 files changed, 157 insertions(+), 134 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e23c8ec1..b81d8a94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,9 +29,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -44,15 +44,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -79,9 +79,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "approx" @@ -139,9 +139,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.56" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", "shlex", @@ -155,9 +155,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.5.59" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5caf74d17c3aec5495110c34cc3f78644bfa89af6c8993ed4de2790e49b6499" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", "clap_derive", @@ -165,9 +165,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.59" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "370daa45065b80218950227371916a1633217ae42b2715b2287b606dcd618e24" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -177,9 +177,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.55" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" dependencies = [ "heck", "proc-macro2", @@ -189,9 +189,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cmtool" @@ -288,9 +288,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "crc32fast" @@ -672,9 +672,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.182" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "link-cplusplus" @@ -717,9 +717,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lz4_flex" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a" +checksum = "373f5eceeeab7925e0c1098212f2fbc4d416adec9d35051a6ab251e824c1854a" dependencies = [ "twox-hash", ] @@ -915,9 +915,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -962,9 +962,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" @@ -980,9 +980,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" +checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" dependencies = [ "portable-atomic", ] @@ -1007,9 +1007,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.28.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c738662e2181be11cb82487628404254902bb3225d8e9e99c31f3ef82a405c" +checksum = "cf85e27e86080aafd5a22eae58a162e133a589551542b3e5cee4beb27e54f8e1" dependencies = [ "libc", "once_cell", @@ -1021,18 +1021,18 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.28.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9ca0864a7dd3c133a7f3f020cbff2e12e88420da854c35540fd20ce2d60e435" +checksum = "8bf94ee265674bf76c09fa430b0e99c26e319c945d96ca0d5a8215f31bf81cf7" dependencies = [ "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.28.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dfc1956b709823164763a34cc42bbfd26b8730afa77809a3df8b94a3ae3b059" +checksum = "491aa5fc66d8059dd44a75f4580a2962c1862a1c2945359db36f6c2818b748dc" dependencies = [ "libc", "pyo3-build-config", @@ -1040,9 +1040,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.28.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29dc660ad948bae134d579661d08033fbb1918f4529c3bbe3257a68f2009ddf2" +checksum = "f5d671734e9d7a43449f8480f8b38115df67bef8d21f76837fa75ee7aaa5e52e" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -1052,9 +1052,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.28.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78cd6c6d718acfcedf26c3d21fe0f053624368b0d44298c55d7138fde9331f7" +checksum = "22faaa1ce6c430a1f71658760497291065e6450d7b5dc2bcf254d49f66ee700a" dependencies = [ "heck", "proc-macro2", @@ -1085,9 +1085,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -1152,9 +1152,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "rustc-hash" @@ -1299,9 +1299,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.116" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1523,9 +1523,9 @@ checksum = "b8aa498d22c9bbaf482329839bc5620c46be275a19a812e9a22a2b07529a642a" [[package]] name = "xsd-parser" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f871bee4eae8bccc80f30d5f5f6cbc14f14b80af05e2ee8ccf680a6296bd006" +checksum = "cf636856509b72e6ce6d84232fdf0a6fb48ce9a04767bf6dc4f7292fa852940e" dependencies = [ "Inflector", "anyhow", @@ -1548,9 +1548,9 @@ dependencies = [ [[package]] name = "xsd-parser-types" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66abb5084a0e2687ec86fd399f4663adeed800f83cdb504070bd1a02027fb397" +checksum = "5a6c01d5f57551a048047e9f28ed5952e3ceab84882832e4b11a748c09122a4a" dependencies = [ "encoding_rs", "indexmap", diff --git a/Cargo.toml b/Cargo.toml index 9c311a9d..54397c06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ serde_xml = "0.9.1" thiserror = "2.0.12" enum_dispatch = "0.3.13" clap = { version = "4.5.41", features = ["derive"] } -xsd-parser = "1.3.0" +xsd-parser = "1.5.0" # nalgebra = "0.34.1" nalgebra-sparse = "0.11.0" diff --git a/cmtool-assemble/build.rs b/cmtool-assemble/build.rs index 50435e11..774b4f3e 100644 --- a/cmtool-assemble/build.rs +++ b/cmtool-assemble/build.rs @@ -5,28 +5,21 @@ use std::fs; use std::io::Write; use xsd_parser::{ Config, Error, - config::{GeneratorFlags, InterpreterFlags, OptimizerFlags, RenderStep, Schema}, + config::{GeneratorFlags, InterpreterFlags, OptimizerFlags, ParserFlags, RenderStep, Schema}, generate, }; static ROOT: &str = "./datamodel"; -fn domain_schema() -> Result<(), Error> { - let files = [ - format!("{}/units.xsd", ROOT), - format!("{}/reactors.xsd", ROOT), - format!("{}/connections.xsd", ROOT), - format!("{}/main.xsd", ROOT), - ]; - - let mut cfg = Config::default(); - cfg.parser.schemas = files - .into_iter() - .map(|f| { - println!("cargo:rerun-if-changed={}", f); - Schema::File(f.into()) - }) - .collect(); +fn domain_schema() -> Result<(), Box> { + // let files = [ + // format!("{}/units.xsd", ROOT), + // format!("{}/reactors.xsd", ROOT), + // format!("{}/connections.xsd", ROOT), + // format!("{}/main.xsd", ROOT), + // ]; + let mut cfg = Config::default().with_schema(Schema::File(format!("{}/main.xsd", ROOT).into())); + cfg = cfg.set_parser_flags(ParserFlags::RESOLVE_INCLUDES | ParserFlags::DEFAULT_NAMESPACES); cfg = cfg.with_render_steps([ //RenderStep::Types, RenderStep::Defaults, @@ -40,26 +33,39 @@ fn domain_schema() -> Result<(), Error> { // }, // RenderStep::TypesSerdeQuickXml, ]); - cfg = cfg.with_derive(["Debug", "Clone"]); + cfg.interpreter.flags = InterpreterFlags::all() - InterpreterFlags::WITH_NUM_BIG_INT - InterpreterFlags::WITH_XS_ANY_TYPE; + cfg.optimizer.flags = OptimizerFlags::all(); - cfg.generator.flags.insert(GeneratorFlags::all()); + + cfg.generator.flags = GeneratorFlags::all() + - GeneratorFlags::USE_MODULES + - GeneratorFlags::USE_NAMESPACE_MODULES + - GeneratorFlags::USE_SCHEMA_MODULES; + let code = generate(cfg).expect("Failed to generate Rust code from XSD"); - let mut file = File::create("src/parser/generated_domain.rs")?; + + let mut file = File::create("src/parser/generated_domain.rs").unwrap(); + file.write_all( - b"#![allow(clippy::all)]\n - #![allow(dead_code)]\n - #![allow(unused_imports)]\n\n\n", - )?; - file.write_all(code.to_string().as_bytes())?; + b" +#![allow(clippy::all)] +#![allow(dead_code)] +#![allow(unused_imports)] +", + ) + .unwrap(); + + file.write_all(code.to_string().as_bytes()).unwrap(); Ok(()) } -fn main() -> Result<(), Error> { +fn main() -> Result<(), Box> { domain_schema()?; + println!("cargo:rerun-if-changed=cmtool-assemble/build.rs"); Ok(()) } diff --git a/cmtool-assemble/datamodel/connections.xsd b/cmtool-assemble/datamodel/connections.xsd index 906230d4..26062269 100644 --- a/cmtool-assemble/datamodel/connections.xsd +++ b/cmtool-assemble/datamodel/connections.xsd @@ -1,30 +1,35 @@ - - - + + - - + + - - - + + + - + - + @@ -32,7 +37,7 @@ - + @@ -40,10 +45,10 @@ - - + + - + @@ -61,7 +66,12 @@ - + diff --git a/cmtool-assemble/datamodel/main.xsd b/cmtool-assemble/datamodel/main.xsd index 731475f8..08e9951d 100644 --- a/cmtool-assemble/datamodel/main.xsd +++ b/cmtool-assemble/datamodel/main.xsd @@ -1,24 +1,29 @@ - - - - - - + + + + - - - + + + - - + + diff --git a/cmtool-assemble/datamodel/reactors.xsd b/cmtool-assemble/datamodel/reactors.xsd index cc41e2fa..16f0ab97 100644 --- a/cmtool-assemble/datamodel/reactors.xsd +++ b/cmtool-assemble/datamodel/reactors.xsd @@ -1,12 +1,12 @@ - - + + @@ -18,9 +18,9 @@ - + - + diff --git a/cmtool-assemble/datamodel/units.xsd b/cmtool-assemble/datamodel/units.xsd index 654e7ab1..7010e4e0 100644 --- a/cmtool-assemble/datamodel/units.xsd +++ b/cmtool-assemble/datamodel/units.xsd @@ -1,14 +1,17 @@ - + - + - + - + @@ -77,6 +80,7 @@ + diff --git a/cmtool-assemble/src/map_generation.rs b/cmtool-assemble/src/map_generation.rs index 4660112c..0a4fea47 100644 --- a/cmtool-assemble/src/map_generation.rs +++ b/cmtool-assemble/src/map_generation.rs @@ -79,7 +79,7 @@ fn _generate_reactor_1d( eprintln!("TODO: PFR GENERATION W/O FLOW RATES"); let desc = PFRDescription { - n_compartment: current_pfr.compartments, + n_compartment: current_pfr.compartments.get(), length: dim.length.content.into(), diameter: dim.diameter.content.into(), liquid_flow: mb.get_flow(¤t_pfr.id, PhaseCM::Liquid)?, diff --git a/cmtool-assemble/src/parser/generated_domain.rs b/cmtool-assemble/src/parser/generated_domain.rs index 9205874e..4875a736 100644 --- a/cmtool-assemble/src/parser/generated_domain.rs +++ b/cmtool-assemble/src/parser/generated_domain.rs @@ -1,8 +1,5 @@ -#![allow(clippy::all)] - - #![allow(dead_code)] - - #![allow(unused_imports)] - -use serde :: { Deserialize , Serialize } ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct AutoFeedType { # [serde (rename = "@phase")] pub phase : :: std :: string :: String , # [serde (rename = "D")] pub d : NodeType , # [serde (rename = "N")] pub n : NodeType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct BaseReactorType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct ConnectionsType { # [serde (default , rename = "Flux")] pub flux : :: std :: vec :: Vec < FluxType > , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct DimensionType { # [serde (default , rename = "@unit")] pub unit : :: core :: option :: Option < :: std :: string :: String > , # [serde (rename = "#text")] pub content : :: core :: primitive :: f32 , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct FeedFluxType { # [serde (rename = "@phase")] pub phase : :: std :: string :: String , # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "Source")] pub source : NodeType , # [serde (rename = "Target")] pub target : NodeType , # [serde (rename = "Value")] pub value : DimensionType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct FeedsType { # [serde (default , rename = "Flux")] pub flux : :: std :: vec :: Vec < FeedFluxType > , } pub type FlowType = DimensionType ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct FluxType { # [serde (rename = "@phase")] pub phase : :: std :: string :: String , # [serde (rename = "Source")] pub source : NodeType , # [serde (rename = "Target")] pub target : NodeType , # [serde (rename = "Value")] pub value : DimensionType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub enum GeneralSizeType { # [serde (rename = "Volume")] Volume (DimensionType) , # [serde (rename = "Dimension")] Dimension (SizeCylindricalType) , } pub type LengthType = DimensionType ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct NodeType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "@compartment_id")] pub compartment_id : :: core :: primitive :: usize , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct Reactor0DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct Reactor1DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , # [serde (rename = "Compartments")] pub compartments : :: core :: primitive :: usize , # [serde (rename = "Dispersion")] pub dispersion : DimensionType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct Reactor3DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , # [serde (rename = "file")] pub file : :: std :: string :: String , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct ReactorFromFileType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "Path")] pub path : :: std :: string :: String , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct ReactorsType { # [serde (rename = "#content")] pub content : :: std :: vec :: Vec < ReactorsTypeContent > , } # [derive (Clone , Debug , Deserialize , Serialize)] pub enum ReactorsTypeContent { # [serde (rename = "Reactor0D")] Reactor0D (Reactor0DType) , # [serde (rename = "Reactor1D")] Reactor1D (Reactor1DType) , # [serde (rename = "Reactor3D")] Reactor3D (Reactor3DType) , # [serde (rename = "ReactorFromFile")] ReactorFromFile (ReactorFromFileType) , } pub type Root = RootElementType ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct RootElementType { # [serde (rename = "@run_id")] pub run_id : :: std :: string :: String , # [serde (rename = "@version")] pub version : :: core :: primitive :: i32 , # [serde (rename = "Reactors")] pub reactors : ReactorsType , # [serde (default , rename = "Connections")] pub connections : :: core :: option :: Option < ConnectionsType > , # [serde (default , rename = "Feeds")] pub feeds : :: core :: option :: Option < FeedsType > , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct SizeCylindricalType { # [serde (rename = "Diameter")] pub diameter : DimensionType , # [serde (rename = "Length")] pub length : DimensionType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub enum SizeSpecificationType { # [serde (rename = "Volume")] Volume (DimensionType) , # [serde (rename = "CylindricalDimensions")] CylindricalDimensions (SizeSpecificationCylindricalDimensionsElementType) , } pub type TimeType = DimensionType ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct VolumeFractionType { # [serde (default , rename = "@phase")] pub phase : :: core :: option :: Option < :: std :: string :: String > , # [serde (rename = "#text")] pub content : :: core :: primitive :: f32 , } pub type VolumeType = DimensionType ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct SizeSpecificationCylindricalDimensionsElementType { # [serde (rename = "Diameter")] pub diameter : DimensionType , # [serde (rename = "Length")] pub length : DimensionType , } pub mod xs { use serde :: { Deserialize , Serialize } ; # [derive (Clone , Debug , Default , Deserialize , Serialize)] pub struct EntitiesType (pub :: std :: vec :: Vec < :: std :: string :: String >) ; pub type EntityType = EntitiesType ; pub type IdType = :: std :: string :: String ; pub type IdrefType = :: std :: string :: String ; pub type IdrefsType = EntitiesType ; pub type NcNameType = :: std :: string :: String ; pub type NmtokenType = :: std :: string :: String ; pub type NmtokensType = EntitiesType ; pub type NotationType = :: std :: string :: String ; pub type NameType = :: std :: string :: String ; pub type QNameType = :: std :: string :: String ; pub type AnySimpleType = :: std :: string :: String ; pub type AnyUriType = :: std :: string :: String ; pub type Base64BinaryType = :: std :: string :: String ; pub type BooleanType = :: core :: primitive :: bool ; pub type ByteType = :: core :: primitive :: i8 ; pub type DateType = :: std :: string :: String ; pub type DateTimeType = :: std :: string :: String ; pub type DecimalType = :: core :: primitive :: f64 ; pub type DoubleType = :: core :: primitive :: f64 ; pub type DurationType = :: std :: string :: String ; pub type FloatType = :: core :: primitive :: f32 ; pub type GDayType = :: std :: string :: String ; pub type GMonthType = :: std :: string :: String ; pub type GMonthDayType = :: std :: string :: String ; pub type GYearType = :: std :: string :: String ; pub type GYearMonthType = :: std :: string :: String ; pub type HexBinaryType = :: std :: string :: String ; pub type IntType = :: core :: primitive :: i32 ; pub type IntegerType = :: core :: primitive :: i32 ; pub type LanguageType = :: std :: string :: String ; pub type LongType = :: core :: primitive :: i64 ; pub type NegativeIntegerType = :: core :: primitive :: isize ; pub type NonNegativeIntegerType = :: core :: primitive :: usize ; pub type NonPositiveIntegerType = :: core :: primitive :: isize ; pub type NormalizedStringType = :: std :: string :: String ; pub type PositiveIntegerType = :: core :: primitive :: usize ; pub type ShortType = :: core :: primitive :: i16 ; pub type StringType = :: std :: string :: String ; pub type TimeType = :: std :: string :: String ; pub type TokenType = :: std :: string :: String ; pub type UnsignedByteType = :: core :: primitive :: u8 ; pub type UnsignedIntType = :: core :: primitive :: u32 ; pub type UnsignedLongType = :: core :: primitive :: u64 ; pub type UnsignedShortType = :: core :: primitive :: u16 ; } \ No newline at end of file +#![allow(clippy::all)] +#![allow(dead_code)] +#![allow(unused_imports)] +use serde :: { Deserialize , Serialize } ; pub type Root = RootElementType ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct RootElementType { # [serde (rename = "@run_id")] pub run_id : :: std :: string :: String , # [serde (rename = "@version")] pub version : :: core :: primitive :: i32 , # [serde (rename = "Reactors")] pub reactors : ReactorsType , # [serde (default , rename = "Connections")] pub connections : :: core :: option :: Option < ConnectionsType > , # [serde (default , rename = "Feeds")] pub feeds : :: core :: option :: Option < FeedsType > , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct DimensionType { # [serde (default , rename = "@unit")] pub unit : :: core :: option :: Option < :: std :: string :: String > , # [serde (rename = "#text")] pub content : :: core :: primitive :: f32 , } pub type FlowType = DimensionType ; pub type LengthType = DimensionType ; # [derive (Clone , Debug , Deserialize , Serialize)] pub enum SizeSpecificationType { # [serde (rename = "Volume")] Volume (DimensionType) , # [serde (rename = "CylindricalDimensions")] CylindricalDimensions (SizeSpecificationCylindricalDimensionsElementType) , } pub type VolumeType = DimensionType ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct AutoFeedType { # [serde (rename = "@phase")] pub phase : :: std :: string :: String , # [serde (rename = "D")] pub d : NodeType , # [serde (rename = "N")] pub n : NodeType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct ConnectionsType { # [serde (default , rename = "Flux")] pub flux : :: std :: vec :: Vec < FluxType > , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct FeedFluxType { # [serde (rename = "@phase")] pub phase : :: std :: string :: String , # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "Source")] pub source : NodeType , # [serde (rename = "Target")] pub target : NodeType , # [serde (rename = "Value")] pub value : DimensionType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct FeedsType { # [serde (default , rename = "Flux")] pub flux : :: std :: vec :: Vec < FeedFluxType > , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct FluxType { # [serde (rename = "@phase")] pub phase : :: std :: string :: String , # [serde (rename = "Source")] pub source : NodeType , # [serde (rename = "Target")] pub target : NodeType , # [serde (rename = "Value")] pub value : DimensionType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct NodeType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "@compartment_id")] pub compartment_id : :: core :: primitive :: usize , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct BaseReactorType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub enum GeneralSizeType { # [serde (rename = "Volume")] Volume (DimensionType) , # [serde (rename = "Dimension")] Dimension (SizeCylindricalType) , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct Reactor0DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct Reactor1DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , # [serde (rename = "Compartments")] pub compartments : :: core :: num :: NonZeroUsize , # [serde (rename = "Dispersion")] pub dispersion : DimensionType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct Reactor3DType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "VolumeFraction")] pub volume_fraction : VolumeFractionType , # [serde (rename = "Size")] pub size : GeneralSizeType , # [serde (rename = "file")] pub file : :: std :: string :: String , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct ReactorFromFileType { # [serde (rename = "@id")] pub id : :: std :: string :: String , # [serde (rename = "Path")] pub path : :: std :: string :: String , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct ReactorsType { # [serde (rename = "#content")] pub content : :: std :: vec :: Vec < ReactorsTypeContent > , } # [derive (Clone , Debug , Deserialize , Serialize)] pub enum ReactorsTypeContent { # [serde (rename = "Reactor0D")] Reactor0D (Reactor0DType) , # [serde (rename = "Reactor1D")] Reactor1D (Reactor1DType) , # [serde (rename = "Reactor3D")] Reactor3D (Reactor3DType) , # [serde (rename = "ReactorFromFile")] ReactorFromFile (ReactorFromFileType) , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct SizeCylindricalType { # [serde (rename = "Diameter")] pub diameter : DimensionType , # [serde (rename = "Length")] pub length : DimensionType , } # [derive (Clone , Debug , Deserialize , Serialize)] pub struct VolumeFractionType { # [serde (default , rename = "@phase")] pub phase : :: core :: option :: Option < :: std :: string :: String > , # [serde (rename = "#text")] pub content : :: core :: primitive :: f32 , } # [derive (Clone , Debug , Default , Deserialize , Serialize)] pub struct EntitiesType (pub :: std :: vec :: Vec < :: std :: string :: String >) ; pub type EntityType = :: std :: string :: String ; pub type IdType = :: std :: string :: String ; pub type IdrefType = :: std :: string :: String ; pub type IdrefsType = EntitiesType ; pub type NcNameType = :: std :: string :: String ; pub type NmtokenType = :: std :: string :: String ; pub type NmtokensType = EntitiesType ; pub type NotationType = :: std :: string :: String ; pub type NameType = :: std :: string :: String ; pub type QNameType = :: std :: string :: String ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct AnySimpleType { # [serde (default , rename = "@xsi:type")] pub type_ : :: core :: option :: Option < :: std :: string :: String > , # [serde (default , rename = "#text")] pub content : :: std :: string :: String , } pub type AnyUriType = :: std :: string :: String ; pub type Base64BinaryType = :: std :: string :: String ; pub type BooleanType = :: core :: primitive :: bool ; pub type ByteType = :: core :: primitive :: i8 ; pub type DateType = :: std :: string :: String ; pub type DateTimeType = :: std :: string :: String ; pub type DecimalType = :: core :: primitive :: f64 ; pub type DoubleType = :: core :: primitive :: f64 ; pub type DurationType = :: std :: string :: String ; pub type FloatType = :: core :: primitive :: f32 ; pub type GDayType = :: std :: string :: String ; pub type GMonthType = :: std :: string :: String ; pub type GMonthDayType = :: std :: string :: String ; pub type GYearType = :: std :: string :: String ; pub type GYearMonthType = :: std :: string :: String ; pub type HexBinaryType = :: std :: string :: String ; pub type IntType = :: core :: primitive :: i32 ; pub type IntegerType = :: core :: primitive :: i32 ; pub type LanguageType = :: std :: string :: String ; pub type LongType = :: core :: primitive :: i64 ; pub type NegativeIntegerType = :: core :: num :: NonZeroIsize ; pub type NonNegativeIntegerType = :: core :: primitive :: usize ; pub type NonPositiveIntegerType = :: core :: primitive :: isize ; pub type NormalizedStringType = :: std :: string :: String ; pub type PositiveIntegerType = :: core :: num :: NonZeroUsize ; pub type ShortType = :: core :: primitive :: i16 ; pub type StringType = :: std :: string :: String ; pub type TimeType = :: std :: string :: String ; pub type TokenType = :: std :: string :: String ; pub type UnsignedByteType = :: core :: primitive :: u8 ; pub type UnsignedIntType = :: core :: primitive :: u32 ; pub type UnsignedLongType = :: core :: primitive :: u64 ; pub type UnsignedShortType = :: core :: primitive :: u16 ; # [derive (Clone , Debug , Deserialize , Serialize)] pub struct SizeSpecificationCylindricalDimensionsElementType { # [serde (rename = "Diameter")] pub diameter : DimensionType , # [serde (rename = "Length")] pub length : DimensionType , } \ No newline at end of file diff --git a/cmtool-assemble/src/parser/reactors.rs b/cmtool-assemble/src/parser/reactors.rs index 9dbadb34..1238cc7c 100644 --- a/cmtool-assemble/src/parser/reactors.rs +++ b/cmtool-assemble/src/parser/reactors.rs @@ -167,10 +167,10 @@ pub fn parse_reactor(reactors: &generated_domain::ReactorsType) -> Result { let path = format!("{}/cma_case", reactor_from_file.path); diff --git a/flake.nix b/flake.nix index f0b7f9fd..63b32e01 100644 --- a/flake.nix +++ b/flake.nix @@ -15,7 +15,7 @@ overlays = [ fenix.overlays.default ]; pkgs = import nixpkgs { inherit system overlays; }; lib = pkgs.lib; - + craneLib = (crane.mkLib pkgs).overrideToolchain (p: p.fenix.stable.withComponents [ "cargo" "clippy" @@ -29,11 +29,11 @@ src = lib.fileset.toSource { root = unfilteredRoot; fileset = lib.fileset.unions [ + (lib.fileset.fileFilter (file: file.hasExt "xsd") unfilteredRoot) # Default files from crane (Rust and cargo files) (craneLib.fileset.commonCargoSources unfilteredRoot) - # Also keep any VTK files, this is a dirty fix for tests which use our example vtk file - # TODO: VTK files should be excluded to avoid indexing of residual output files - (lib.fileset.fileFilter (file: file.hasExt "vtk") unfilteredRoot) + + ]; }; @@ -105,10 +105,10 @@ } ); }; - + devShells.default = craneLib.devShell { checks = self.checks.${system}; - + packages = with pkgs; [ cargo-nextest # faster tests samply # profiling From fa4819b669cd6175493b6de1ce289b2b54f6d8f7 Mon Sep 17 00:00:00 2001 From: bcasale Date: Wed, 18 Mar 2026 14:05:11 +0100 Subject: [PATCH 37/44] fix(nix): working flake check --- README.md | 13 +++++--- cmtool-core/src/coordinates/vec3.rs | 3 +- cmtool-core/src/grid/mod.rs | 42 ++++++++++++------------ cmtool-data/src/flowmap.rs | 16 +++++---- cmtool-data/src/states.rs | 15 +++++---- documentations/docbook/src/cfd_to_cma.md | 1 + flake.nix | 21 ++++++------ 7 files changed, 60 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index ad02af97..003c4c69 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,12 @@ The specific goal of this repository is to develop a Compartment Modelling Tool. Compartmental Modeling Approach (CMA) is a method that simplifies spatial dimensions by dividing the domain into smaller, uniform "compartments." Each compartment behaves as a homogeneous unit, connected to its neighboring compartments. The domain is represented by a scalar field, and the interactions between compartments are governed by a flowmap. This approach significantly reduces computational complexity while maintaining an acceptable level of model accuracy. + +### Objectives + +This crate is a port of an existing C++ tool. The objective of the Rust implementation is to evaluate the efficiency of the Rust language to perform this type of operation, with a particular focus on maintaining code readability, maintainability, and performance. + + ## Overview This crates aims to provides different feature to perform complete simulation using CMA. @@ -22,13 +28,12 @@ This crates aims to provides different feature to perform complete simulation us - cmtool-python: Python bindings for cmtool-core,FlowmapTransitioner and case reading - cmtool-cxx: Bindings for C++ use, expose FlowMapTranstioner API -## Objectives - -This crate is a port of an existing C++ tool. The objective of the Rust implementation is to evaluate the efficiency of the Rust language to perform this type of operation, with a particular focus on maintaining code readability, maintainability, and performance. ## Getting started - +```sh +cargo run --example [NAME] +``` ## Authors - **CASALE Benjamin** diff --git a/cmtool-core/src/coordinates/vec3.rs b/cmtool-core/src/coordinates/vec3.rs index a87aea38..fecb3fc4 100644 --- a/cmtool-core/src/coordinates/vec3.rs +++ b/cmtool-core/src/coordinates/vec3.rs @@ -168,7 +168,7 @@ mod tests { let cyl = cart.to_cylindrical_vec(base_theta); let vr_expected = 1.0 * base_theta.cos() + 0.0 * base_theta.sin(); - let vtheta_expected = -1.0 * base_theta.sin() + 0.0 * base_theta.cos(); + let vtheta_expected = -base_theta.sin() + 0.0 * base_theta.cos(); let vz_expected = 2.0; assert!((cyl.0[0] - vr_expected).abs() < 1e-10); @@ -240,6 +240,7 @@ mod tests { assert!((result - 5.0).abs() < 1e-10); } + #[allow(clippy::needless_range_loop)] #[test] fn test_normalized() { let v = CartesianVec3([0.0, 3.0, 4.0]); diff --git a/cmtool-core/src/grid/mod.rs b/cmtool-core/src/grid/mod.rs index 91767f58..84c682b2 100644 --- a/cmtool-core/src/grid/mod.rs +++ b/cmtool-core/src/grid/mod.rs @@ -823,23 +823,23 @@ mod test { use super::*; - const number_point_ax1: usize = 8; - const max_ax1: f64 = 4.; + const NUMBER_POINT_AX1: usize = 8; + const MAX_AX1: f64 = 4.; - const number_point_ax2: usize = 5; - const max_ax2: f64 = 2.; + const NUMBER_POINT_AX2: usize = 5; + const _MAX_AX2: f64 = 2.; - const number_point_ax3: usize = 10; - const max_ax3: f64 = 10.; + const NUMBER_POINT_AX3: usize = 10; + const MAX_AX3: f64 = 10.; fn ref_mesh_cyclindrical() -> Box { - let ax1 = AxisDescriptor::new(0., max_ax1, number_point_ax1); + let ax1 = AxisDescriptor::new(0., MAX_AX1, NUMBER_POINT_AX1); let ax2 = AxisDescriptor::new( -std::f64::consts::PI, std::f64::consts::PI, - number_point_ax2, + NUMBER_POINT_AX2, ); - let ax3 = AxisDescriptor::new(0., max_ax3, number_point_ax3); + let ax3 = AxisDescriptor::new(0., MAX_AX3, NUMBER_POINT_AX3); get_mesh(MeshType::Cylindrical, [ax1, ax2, ax3]) } @@ -936,17 +936,17 @@ mod test { #[test] fn t_getter() { let mesh = ref_mesh_cyclindrical(); - assert!(mesh.max_axis(0) == max_ax1); + assert!(mesh.max_axis(0) == MAX_AX1); assert!(mesh.max_axis(1) == std::f64::consts::PI); - assert!(mesh.max_axis(2) == max_ax3); + assert!(mesh.max_axis(2) == MAX_AX3); assert!(mesh.min_axis(0) == 0.); assert!(mesh.min_axis(1) == -std::f64::consts::PI); - assert!(mesh.n_points_axis(0) == number_point_ax1); - assert!(mesh.n_points_axis(1) == number_point_ax2); - assert!(mesh.n_points_axis(2) == number_point_ax3); - assert!(mesh.number_cell() == number_point_ax1 * number_point_ax2 * number_point_ax3); + assert!(mesh.n_points_axis(0) == NUMBER_POINT_AX1); + assert!(mesh.n_points_axis(1) == NUMBER_POINT_AX2); + assert!(mesh.n_points_axis(2) == NUMBER_POINT_AX3); + assert!(mesh.number_cell() == NUMBER_POINT_AX1 * NUMBER_POINT_AX2 * NUMBER_POINT_AX3); } #[test] @@ -970,22 +970,22 @@ mod test { assert_id([0., -std::f64::consts::PI, 0.], 0); - assert_id([0., -std::f64::consts::PI, max_ax3], number_point_ax3 - 1); + assert_id([0., -std::f64::consts::PI, MAX_AX3], NUMBER_POINT_AX3 - 1); let theta = -std::f64::consts::PI + mesh.mesh_step_axis(1) * 1.1; //R!=0 because with cartesian conversion is x=rcos(theta) if theta changes but no r its the same compartment - assert_id([0.01, theta, 0.], number_point_ax3); + assert_id([0.01, theta, 0.], NUMBER_POINT_AX3); //-1 because we consider cell ID for 0 to n-1 assert_id( - [max_ax1, std::f64::consts::PI, max_ax3], - (number_point_ax3 * number_point_ax1 * number_point_ax2) - 1, + [MAX_AX1, std::f64::consts::PI, MAX_AX3], + (NUMBER_POINT_AX3 * NUMBER_POINT_AX1 * NUMBER_POINT_AX2) - 1, ); } #[test] fn t_boundary_cylindrical() { - let ax1 = AxisDescriptor::new(0., max_ax1, 5); + let ax1 = AxisDescriptor::new(0., MAX_AX1, 5); let ax2 = AxisDescriptor::new(-std::f64::consts::PI, std::f64::consts::PI, 10); - let ax3 = AxisDescriptor::new(0., max_ax3, 10); + let ax3 = AxisDescriptor::new(0., MAX_AX3, 10); let mesh = get_mesh(MeshType::Cylindrical, [ax1, ax2, ax3]); let mut v = mesh.get_boundary(); diff --git a/cmtool-data/src/flowmap.rs b/cmtool-data/src/flowmap.rs index b488306e..2a2226a4 100644 --- a/cmtool-data/src/flowmap.rs +++ b/cmtool-data/src/flowmap.rs @@ -84,15 +84,17 @@ mod test { #[test] fn read_descriptor() { - let flow_cma = std::env::var("CUVE_SLDMSH_FLOW_PATH").unwrap(); + let _flow_cma = std::env::var("CUVE_SLDMSH_FLOW_PATH"); - let volume_cma = std::env::var("CUVE_SLDMSH_VOLUME_PATH").unwrap(); + let _volume_cma = std::env::var("CUVE_SLDMSH_VOLUME_PATH"); - let descriptor = FlowMapDescriptor::from_path(flow_cma, volume_cma).unwrap(); + if let (Ok(flow_cma), Ok(volume_cma)) = (_flow_cma, _volume_cma) { + let descriptor = FlowMapDescriptor::from_path(flow_cma, volume_cma).unwrap(); - assert!(!descriptor.volumes.is_empty()); - assert!(descriptor.flowmap.is_square()); - assert!(descriptor.volumes.len() == descriptor.flowmap.ncols()); - assert!(descriptor.neighbors.nrows() == descriptor.flowmap.ncols()); + assert!(!descriptor.volumes.is_empty()); + assert!(descriptor.flowmap.is_square()); + assert!(descriptor.volumes.len() == descriptor.flowmap.ncols()); + assert!(descriptor.neighbors.nrows() == descriptor.flowmap.ncols()); + } } } diff --git a/cmtool-data/src/states.rs b/cmtool-data/src/states.rs index 86be974a..12e5b98c 100644 --- a/cmtool-data/src/states.rs +++ b/cmtool-data/src/states.rs @@ -174,16 +174,17 @@ mod test { #[test] fn construct_itstate_liquid_only() { - let flow_cma = std::env::var("CUVE_SLDMSH_FLOW_PATH").unwrap(); + let _flow_cma = std::env::var("CUVE_SLDMSH_FLOW_PATH"); + let _volume_cma = std::env::var("CUVE_SLDMSH_VOLUME_PATH"); - let volume_cma = std::env::var("CUVE_SLDMSH_VOLUME_PATH").unwrap(); + if let (Ok(flow_cma), Ok(volume_cma)) = (_flow_cma, _volume_cma) { + let descriptor = FlowMapDescriptor::from_path(flow_cma, volume_cma).unwrap(); - let descriptor = FlowMapDescriptor::from_path(flow_cma, volume_cma).unwrap(); + let vol_ref = descriptor.volumes.clone(); - let vol_ref = descriptor.volumes.clone(); + let state = IterationState::new(descriptor, None, HashMap::new()); - let state = IterationState::new(descriptor, None, HashMap::new()); - - assert!(state.liquid.volumes == vol_ref); + assert!(state.liquid.volumes == vol_ref); + } } } diff --git a/documentations/docbook/src/cfd_to_cma.md b/documentations/docbook/src/cfd_to_cma.md index eca536ba..3c67a400 100644 --- a/documentations/docbook/src/cfd_to_cma.md +++ b/documentations/docbook/src/cfd_to_cma.md @@ -1,3 +1,4 @@ # CFD-To-CMA +Work in progess partially working feature According to @@computational_engineering_international_inc_ensight_2003, ... diff --git a/flake.nix b/flake.nix index 63b32e01..7d438fae 100644 --- a/flake.nix +++ b/flake.nix @@ -30,10 +30,9 @@ root = unfilteredRoot; fileset = lib.fileset.unions [ (lib.fileset.fileFilter (file: file.hasExt "xsd") unfilteredRoot) - # Default files from crane (Rust and cargo files) (craneLib.fileset.commonCargoSources unfilteredRoot) - - + (unfilteredRoot + "/cmtool-data/test_data") + (unfilteredRoot + "/examples/data") ]; }; @@ -47,18 +46,18 @@ commonArgs = { inherit src; strictDeps = true; - nativeBuildInputs = with pkgs; [ pkg-config ]; buildInputs = commonBuildInputs ++ (if pkgs.stdenv.isLinux then linuxBuildInputs else []) ++ (if pkgs.stdenv.isDarwin then darwinBuildInputs else []); - # LD_LIBRARY_PATH = "$LD_LIBRARY_PATH:${ - # pkgs.lib.makeLibraryPath ( commonBuildInputs - # ++ (if pkgs.stdenv.isLinux then linuxBuildInputs else []) - # ++ (if pkgs.stdenv.isDarwin then darwinBuildInputs else []) ) - # }"; + LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath ( + commonBuildInputs + ++ (if pkgs.stdenv.isLinux then linuxBuildInputs else []) + ++ (if pkgs.stdenv.isDarwin then darwinBuildInputs else []) + ++ [ pkgs.stdenv.cc.cc.lib ] + ); }; cargoArtifacts = craneLib.buildDepsOnly commonArgs; @@ -110,9 +109,9 @@ checks = self.checks.${system}; packages = with pkgs; [ - cargo-nextest # faster tests + cargo-nextest samply # profiling - taplo # TOML formatting + ]; }; }); From d905de25524800ea73645602a1ec8d34d7e56222 Mon Sep 17 00:00:00 2001 From: bcasale Date: Mon, 23 Mar 2026 09:13:22 +0100 Subject: [PATCH 38/44] fix(core): find a new way to calculate flows with null divergence (#8) --- cmtool-core/src/coordinates/mod.rs | 16 +- cmtool-core/src/errors.rs | 6 + cmtool-core/src/grid/mod.rs | 444 ++++---------------------- cmtool-core/src/grid/tests.rs | 440 +++++++++++++++++++++++++ cmtool-core/src/model/compartments.rs | 5 +- cmtool-core/src/model/geometry.rs | 57 +--- cmtool-core/src/model/interfaces.rs | 96 ++++-- cmtool-core/src/model/mod.rs | 233 +++++++++++--- cmtool-core/src/utils/area.rs | 226 ++++++------- cmtool/src/main.rs | 14 +- examples/python/mixing_simple.py | 2 +- 11 files changed, 905 insertions(+), 634 deletions(-) create mode 100644 cmtool-core/src/grid/tests.rs diff --git a/cmtool-core/src/coordinates/mod.rs b/cmtool-core/src/coordinates/mod.rs index 0886df74..ee24e8c2 100644 --- a/cmtool-core/src/coordinates/mod.rs +++ b/cmtool-core/src/coordinates/mod.rs @@ -39,7 +39,7 @@ impl BoundedPlane { let r = (point[0].powi(2) + point[1].powi(2)).sqrt(); let theta_raw = point[1].atan2(point[0]); let z = point[2]; - let tol = 1e-10; + const TOL: f64 = 1e-10; let (u, v) = match self.axis { 0 => { @@ -70,8 +70,8 @@ impl BoundedPlane { _ => unreachable!(), }; - (u >= self.extent_u[0] - tol && u <= self.extent_u[1] + tol) - && (v >= self.extent_v[0] - tol && v <= self.extent_v[1] + tol) + (u >= self.extent_u[0] - TOL && u <= self.extent_u[1] + TOL) + && (v >= self.extent_v[0] - TOL && v <= self.extent_v[1] + TOL) } } @@ -84,6 +84,16 @@ impl From for Plane { } } +pub fn get_normal(axis: usize, negative: bool) -> Coords3 { + let sign = if negative { -1. } else { 1. }; + match axis { + 0 => [sign, 0.0, 0.0], + 1 => [0.0, sign, 0.0], + 2 => [0.0, 0.0, sign], + _ => unreachable!("Axis must be 0, 1, or 2"), + } +} + // pub trait AsCoords3 { // fn as_coords(&self) -> &Coords3; // } diff --git a/cmtool-core/src/errors.rs b/cmtool-core/src/errors.rs index 11b3f5da..fa2c5507 100644 --- a/cmtool-core/src/errors.rs +++ b/cmtool-core/src/errors.rs @@ -2,6 +2,12 @@ use thiserror::Error; +// macro_rules! error_fmt { +// ($name:ident,$msg:literal) => { +// format!("CMTOOL({}):{}", name, msg) +// }; +// } + #[derive(Error, Debug)] pub enum CoreError { #[error("Cmtool: {0}")] diff --git a/cmtool-core/src/grid/mod.rs b/cmtool-core/src/grid/mod.rs index 84c682b2..fe44bfd3 100644 --- a/cmtool-core/src/grid/mod.rs +++ b/cmtool-core/src/grid/mod.rs @@ -2,7 +2,9 @@ mod collections; use collections::*; -pub use collections::{AxisDescriptor, CylindricalAxis, cylindrical_index}; +pub use collections::{ + AxisDescriptor, CylindricalAxis, OrientedAxis, cylindrical_index, index_to_oriented, +}; use enum_dispatch::enum_dispatch; use std::f64; @@ -434,103 +436,14 @@ impl CompartmentMeshManip for MeshCylindrical { interfaces_r + interfaces_theta + interfaces_z + wrap } - // fn get_interface_plane(&self, cell1_id: usize, cell2_id: usize) -> (BoundedPlane, usize) { - // let neighbors = self.are_cell_neighbor(cell1_id, cell2_id); - // let axis = neighbors - // .to_coord_index() - // .expect("Cells must be neighbors to get interface plane"); - - // let sign = if neighbors.is_negative() { -1.0 } else { 1.0 }; - // let normal_dir = match axis { - // 0 => [sign, 0.0, 0.0], - // 1 => [0.0, sign, 0.0], - // 2 => [0.0, 0.0, sign], - // _ => unreachable!("Axis must be 0, 1, or 2"), - // }; - - // let indices_cell = self.cell_points(cell1_id); - - // // Cell edges - // let r0 = self.get_cell_edge(0, indices_cell[0]); - // let r1 = self.get_cell_edge(0, indices_cell[0] + 1); - // let theta0 = self.get_cell_edge(1, indices_cell[1]); - // let theta1 = self.get_cell_edge(1, indices_cell[1] + 1); - // let z0 = self.get_cell_edge(2, indices_cell[2]); - // let z1 = self.get_cell_edge(2, indices_cell[2] + 1); - // let pi = std::f64::consts::PI; - // let normalize = |a: f64| -> f64 { - // let mut x = a % (2.0 * pi); - // if x > pi { - // x -= 2.0 * pi; - // } - // if x < -pi { - // x += 2.0 * pi; - // } - // x - // }; - - // // Centers - // let r_center = 0.5 * (r0 + r1); - - // let z_center = 0.5 * (z0 + z1); - // // let theta_center = 0.5 * (theta0 + theta1); - // let theta_center = { - // let mut dtheta = theta1 - theta0; - // if dtheta > pi { - // dtheta -= 2.0 * pi; - // } - // if dtheta < -pi { - // dtheta += 2.0 * pi; - // } - // normalize(theta0 + 0.5 * dtheta) - // }; - - // // Origin of the plane (on interface) - // let (r, theta, z) = match axis { - // 0 => (if sign < 0.0 { r0 } else { r1 }, theta_center, z_center), - // 1 => (r_center, if sign < 0.0 { theta0 } else { theta1 }, z_center), - // 2 => (r_center, theta_center, if sign < 0.0 { z0 } else { z1 }), - // _ => unreachable!(), - // }; - - // let (extent_u, extent_v) = match axis { - // 0 => ([theta0, theta1], [z0, z1]), - // 1 => ([r0, r1], [z0, z1]), - // 2 => ([r0, r1], [theta0, theta1]), - // _ => unreachable!(), - // }; - - // if axis == 0 { - // let bounded_plane = get_tangent_plane_at_r(axis, r, theta, z, extent_u, extent_v); - // (bounded_plane, axis) - // } else { - // let cyl_normal = CylindricalVec3(normal_dir, theta); - // let normal_cartesian = cyl_normal.to_cartesian_vec(); - // let origin = CylindricalCoordinates([r, theta, z]).into(); - // let bounded_plane = BoundedPlane { - // normal: normal_cartesian, - // origin, - // extent_u, - // extent_v, - // axis, - // }; - // (bounded_plane, axis) - // } - // } - fn get_interface_plane(&self, cell1_id: usize, cell2_id: usize) -> (BoundedPlane, usize) { let neighbors = self.are_cell_neighbor(cell1_id, cell2_id); let axis = neighbors .to_coord_index() - .expect("Cells must be neighbors to get interface plane"); + .expect("RMTOOL(get_interface_plane): Cells must be neighbors to get interface plane"); let sign = if neighbors.is_negative() { -1.0 } else { 1.0 }; - let normal_dir = match axis { - 0 => [sign, 0.0, 0.0], - 1 => [0.0, sign, 0.0], - 2 => [0.0, 0.0, sign], - _ => unreachable!("Axis must be 0, 1, or 2"), - }; + let normal_dir = get_normal(axis, sign == -1.); let indices_cell = self.cell_points(cell1_id); @@ -609,6 +522,71 @@ impl CompartmentMeshManip for MeshCylindrical { } } + // fn get_interface_plane(&self, cell1_id: usize, cell2_id: usize) -> (BoundedPlane, usize) { + // let neighbors = self.are_cell_neighbor(cell1_id, cell2_id); + // let axis = neighbors + // .to_coord_index() + // .expect("Cells must be neighbors to get interface plane"); + + // let sign = if neighbors.is_negative() { -1.0 } else { 1.0 }; + + // let indices_cell = self.cell_points(cell1_id); + + // // Cell edges + // let r0 = self.get_cell_edge(0, indices_cell[0]); + // let r1 = self.get_cell_edge(0, indices_cell[0] + 1); + // let theta0 = self.get_cell_edge(1, indices_cell[1]); + // let theta1 = self.get_cell_edge(1, indices_cell[1] + 1); + // let z0 = self.get_cell_edge(2, indices_cell[2]); + // let z1 = self.get_cell_edge(2, indices_cell[2] + 1); + + // // Midpoints + // let r_center = 0.5 * (r0 + r1); + // let z_center = 0.5 * (z0 + z1); + // let theta_center = 0.5 * (theta0 + theta1); + + // // Extents + // let (extent_u, extent_v) = match axis { + // 0 => ([r0, r1], [z0, z1]), // Plane defined by (r, z) + // 1 => ([theta0, theta1], [z0, z1]), // Plane defined by (theta, z) + // 2 => ([r0, r1], [theta0, theta1]), // Plane defined by (r, theta) + // _ => unreachable!("Axis must be 0, 1, or 2"), + // }; + + // // Origin and normal + // let (origin, normal) = match axis { + // 0 => { + // // Plane defined by (r, z) + // let origin = CartesianCoordinates([r_center, theta_center, z_center]).into(); + // let normal = CartesianVec3([1.0, 0.0, 0.0]); // Radial direction + // (origin, normal) + // } + // 1 => { + // // Plane defined by (theta, z) + // let origin = CartesianCoordinates([r_center, theta_center, z_center]).into(); + // let normal = CartesianVec3([0.0, 1.0, 0.0]); // Angular direction + // (origin, normal) + // } + // 2 => { + // // Plane defined by (r, theta) + // let origin = CartesianCoordinates([r_center, theta_center, z_center]).into(); + // let normal = CartesianVec3([0.0, 0.0, 1.0]); // Axial direction + // (origin, normal) + // } + // _ => unreachable!("Axis must be 0, 1, or 2"), + // }; + + // let bounded_plane = BoundedPlane { + // normal, + // origin, + // extent_u, + // extent_v, + // axis, + // }; + + // (bounded_plane, axis) + // } + fn are_cell_neighbor(&self, cell1_id: usize, cell2_id: usize) -> NeighborDirection { let [r1, theta1, z1] = self.cell_points(cell1_id); let [r2, theta2, z2] = self.cell_points(cell2_id); @@ -819,284 +797,4 @@ pub fn get_mesh( } #[cfg(test)] -mod test { - - use super::*; - - const NUMBER_POINT_AX1: usize = 8; - const MAX_AX1: f64 = 4.; - - const NUMBER_POINT_AX2: usize = 5; - const _MAX_AX2: f64 = 2.; - - const NUMBER_POINT_AX3: usize = 10; - const MAX_AX3: f64 = 10.; - - fn ref_mesh_cyclindrical() -> Box { - let ax1 = AxisDescriptor::new(0., MAX_AX1, NUMBER_POINT_AX1); - let ax2 = AxisDescriptor::new( - -std::f64::consts::PI, - std::f64::consts::PI, - NUMBER_POINT_AX2, - ); - let ax3 = AxisDescriptor::new(0., MAX_AX3, NUMBER_POINT_AX3); - get_mesh(MeshType::Cylindrical, [ax1, ax2, ax3]) - } - - #[test] - fn t_cell_surface_cylindrical() { - let ax1 = AxisDescriptor::new(0., 4., 10); - let ax2 = AxisDescriptor::new(-std::f64::consts::PI, std::f64::consts::PI, 10); - let ax3 = AxisDescriptor::new(0., 2., 10); - - let mesh = get_mesh(MeshType::Cylindrical, [ax1, ax2, ax3]); - - let r_face_center = 0.4; - let dtheta = 2. * std::f64::consts::PI / 10.; - let dz = 2. / 10.; - let expected_surface = r_face_center * dtheta * dz; - let actual_surface = mesh.cell_surface(0, OrientedAxis::I); - assert!( - (actual_surface - expected_surface).abs() < 1e-10, - "Radial face surface incorrect: got {}, expected {}", - actual_surface, - expected_surface - ); - - let dr = 4. / 10.; // 0.4 - let expected_surface = dr * dz; - let actual_surface = mesh.cell_surface(0, OrientedAxis::J); - assert!( - (actual_surface - expected_surface).abs() < 1e-10, - "Theta face surface incorrect: got {}, expected {}", - actual_surface, - expected_surface - ); - - let r1 = 0.0; - let r2 = 0.4; - let dtheta = 2. * std::f64::consts::PI / 10.; - - let expected_surface = 0.5 * (r2 * r2 - r1 * r1) * dtheta; - let actual_surface = mesh.cell_surface(0, OrientedAxis::K); - assert!( - (actual_surface - expected_surface).abs() < 1e-10, - "Axial face surface incorrect: got {}, expected {}", - actual_surface, - expected_surface - ); - } - - #[test] - fn t_cell_volume_cylindrical() { - let ax1 = AxisDescriptor::new(0., 4., 10); - let ax2 = AxisDescriptor::new(-std::f64::consts::PI, std::f64::consts::PI, 10); - let ax3 = AxisDescriptor::new(0., 2., 10); - let mesh = get_mesh(MeshType::Cylindrical, [ax1, ax2, ax3]); - - let r1 = 0.0; - let r2 = 0.4; - let dtheta = 2. * std::f64::consts::PI / 10.; - let dz = 2.0 / 10.0; - - let expected_volume = 0.5 * (r2 * r2 - r1 * r1) * dtheta * dz; - let actual_volume = mesh.cell_volume(0); - assert!( - (expected_volume - actual_volume).abs() < 1e-10, - "Axial face volume incorrect: got {}, expected {}", - actual_volume, - expected_volume - ); - - let expected_full_volume = std::f64::consts::PI * 4. * 4. * 2.; - let full_volume: f64 = (0..mesh.number_cell()).map(|e| mesh.cell_volume(e)).sum(); - assert!( - (expected_full_volume - full_volume).abs() < 1e-10, - "full_volume incorrect: got {}, expected {}", - full_volume, - expected_full_volume - ); - } - - #[test] - fn t_get_mesh() { - let ax1 = AxisDescriptor::new(0.5, 1., 10); - let ax2 = AxisDescriptor::new(-std::f64::consts::PI, std::f64::consts::PI, 20); - let ax3 = AxisDescriptor::new(0., 2., 15); - let expected_step_x = 1. / 10.; //Cylindrical start ax from 0 to max_range - let mesh = get_mesh(MeshType::Cylindrical, [ax1, ax2, ax3]); - assert!( - mesh.mesh_step_axis(0) == expected_step_x, - "{} {}", - mesh.mesh_step_axis(0), - expected_step_x - ); - } - - #[test] - fn t_getter() { - let mesh = ref_mesh_cyclindrical(); - assert!(mesh.max_axis(0) == MAX_AX1); - assert!(mesh.max_axis(1) == std::f64::consts::PI); - assert!(mesh.max_axis(2) == MAX_AX3); - - assert!(mesh.min_axis(0) == 0.); - assert!(mesh.min_axis(1) == -std::f64::consts::PI); - - assert!(mesh.n_points_axis(0) == NUMBER_POINT_AX1); - assert!(mesh.n_points_axis(1) == NUMBER_POINT_AX2); - assert!(mesh.n_points_axis(2) == NUMBER_POINT_AX3); - assert!(mesh.number_cell() == NUMBER_POINT_AX1 * NUMBER_POINT_AX2 * NUMBER_POINT_AX3); - } - - #[test] - fn t_identification_cylindrical() { - let mesh = ref_mesh_cyclindrical(); - - let assert_id = |a: Coords3, expect: usize| { - let CartesianCoordinates(aa) = CylindricalCoordinates(a).into(); - - let id1 = mesh - .cell_from_coordinates(&aa) - .expect("Test neighbors: coordinates for cell a are outside the mesh."); - - assert!( - id1 == expect, - "Assertion failed: expected {:?}, got {:?}", - expect, - id1 - ); - }; - - assert_id([0., -std::f64::consts::PI, 0.], 0); - - assert_id([0., -std::f64::consts::PI, MAX_AX3], NUMBER_POINT_AX3 - 1); - let theta = -std::f64::consts::PI + mesh.mesh_step_axis(1) * 1.1; - //R!=0 because with cartesian conversion is x=rcos(theta) if theta changes but no r its the same compartment - assert_id([0.01, theta, 0.], NUMBER_POINT_AX3); - //-1 because we consider cell ID for 0 to n-1 - assert_id( - [MAX_AX1, std::f64::consts::PI, MAX_AX3], - (NUMBER_POINT_AX3 * NUMBER_POINT_AX1 * NUMBER_POINT_AX2) - 1, - ); - } - - #[test] - fn t_boundary_cylindrical() { - let ax1 = AxisDescriptor::new(0., MAX_AX1, 5); - let ax2 = AxisDescriptor::new(-std::f64::consts::PI, std::f64::consts::PI, 10); - let ax3 = AxisDescriptor::new(0., MAX_AX3, 10); - let mesh = get_mesh(MeshType::Cylindrical, [ax1, ax2, ax3]); - - let mut v = mesh.get_boundary(); - let w = [ - 0, 9, 10, 19, 20, 29, 30, 39, 40, 49, 50, 59, 60, 69, 70, 79, 80, 89, 90, 99, 100, 109, - 110, 119, 120, 129, 130, 139, 140, 149, 150, 159, 160, 169, 170, 179, 180, 189, 190, - 199, 200, 209, 210, 219, 220, 229, 230, 239, 240, 249, 250, 259, 260, 269, 270, 279, - 280, 289, 290, 299, 300, 309, 310, 319, 320, 329, 330, 339, 340, 349, 350, 359, 360, - 369, 370, 379, 380, 389, 390, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, - 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, - 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, - 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, - 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, - 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, - 495, 496, 497, 498, 499, - ]; - v.sort(); - assert_eq!(v, w); - } - - #[test] - fn t_neighbors_cylindrical() { - use NeighborDirection::*; - - let mesh = ref_mesh_cyclindrical(); - - let assert_neighbors = - |a: CylindricalCoordinates, b: CylindricalCoordinates, expected: NeighborDirection| { - let CartesianCoordinates(aa) = a.into(); - - let CartesianCoordinates(bb) = b.into(); - - let id1 = mesh - .cell_from_coordinates(&aa) - .expect("Test neighbors: coordinates for cell a are outside the mesh."); - let id2 = mesh - .cell_from_coordinates(&bb) - .expect("Test neighbors: coordinates for cell b are outside the mesh."); - - let neighbors = mesh.are_cell_neighbor(id1, id2); - assert!( - neighbors == expected, - "Assertion failed: expected {:?}, got {:?}", - expected, - neighbors - ); - }; - - // Fixed coordinates and offsets for testing - let fix_i = 2.2; - let offset_i = mesh.mesh_step_axis(0); - let fix_j = 0.; - let offset_j = 0.68; - let fix_k = 4.0; - let offset_k = 0.9; - - // Assert neighbor relationships in different directions - - // Testing in X direction - assert_neighbors( - CylindricalCoordinates([fix_i, fix_j, fix_k]), - CylindricalCoordinates([fix_i + offset_i, fix_j, fix_k]), - XPlus, - ); - assert_neighbors( - CylindricalCoordinates([fix_i + offset_i, fix_j, fix_k]), - CylindricalCoordinates([fix_i, fix_j, fix_k]), - XMinus, - ); - - // Testing in Y direction - assert_neighbors( - CylindricalCoordinates([fix_i, fix_j + offset_j, fix_k]), - CylindricalCoordinates([fix_i, fix_j, fix_k]), - YMinus, - ); - assert_neighbors( - CylindricalCoordinates([fix_i, fix_j, fix_k]), - CylindricalCoordinates([fix_i, fix_j + offset_j, fix_k]), - YPlus, - ); - - // Testing in Z direction - assert_neighbors( - CylindricalCoordinates([fix_i, fix_j, fix_k + offset_k]), - CylindricalCoordinates([fix_i, fix_j, fix_k]), - ZMinus, - ); - assert_neighbors( - CylindricalCoordinates([fix_i, fix_j, fix_k]), - CylindricalCoordinates([fix_i, fix_j, fix_k + offset_k]), - ZPlus, - ); - - // Testing non-neighbor cases - assert_neighbors( - CylindricalCoordinates([0., fix_j, fix_k + offset_k]), - CylindricalCoordinates([fix_i, fix_j, fix_k]), - NotNeighbors, - ); - assert_neighbors( - CylindricalCoordinates([fix_i, fix_j, fix_k]), - CylindricalCoordinates([0., fix_j, fix_k + offset_k]), - NotNeighbors, - ); - let little_offset = mesh.mesh_step_axis(0) * 1.005; - let c1: f64 = mesh.get_cell_edge(0, 2); - assert_neighbors( - CylindricalCoordinates([c1, fix_j, fix_k]), - CylindricalCoordinates([c1 + little_offset, fix_j, fix_k]), - NotNeighbors, - ); - } -} +mod tests; diff --git a/cmtool-core/src/grid/tests.rs b/cmtool-core/src/grid/tests.rs new file mode 100644 index 00000000..23472ff8 --- /dev/null +++ b/cmtool-core/src/grid/tests.rs @@ -0,0 +1,440 @@ +use super::*; +#[cfg(test)] +mod test { + + use super::*; + + const NUMBER_POINT_AX1: usize = 8; + const MAX_AX1: f64 = 4.; + + const NUMBER_POINT_AX2: usize = 5; + const _MAX_AX2: f64 = 2.; + + const NUMBER_POINT_AX3: usize = 10; + const MAX_AX3: f64 = 10.; + + fn utils_planes_neighbors( + nax1: usize, + nax2: usize, + nax3: usize, + ) -> (Box, f64, f64, f64) { + let ax1 = AxisDescriptor::new(0., MAX_AX1, nax1); + let ax2 = AxisDescriptor::new(-std::f64::consts::PI, std::f64::consts::PI, nax2); + + let ax3 = AxisDescriptor::new(0., MAX_AX3, nax3); + + let mesh = get_mesh(MeshType::Cylindrical, [ax1, ax2, ax3]); + + let step_r = MAX_AX1 / (nax1 as f64); + let step_theta = 2.0 * std::f64::consts::PI / (nax2 as f64); + let step_z = MAX_AX3 / (nax3 as f64); + + (mesh, step_r, step_theta, step_z) + } + + fn ref_mesh_cyclindrical() -> Box { + let ax1 = AxisDescriptor::new(0., MAX_AX1, NUMBER_POINT_AX1); + let ax2 = AxisDescriptor::new( + -std::f64::consts::PI, + std::f64::consts::PI, + NUMBER_POINT_AX2, + ); + let ax3 = AxisDescriptor::new(0., MAX_AX3, NUMBER_POINT_AX3); + get_mesh(MeshType::Cylindrical, [ax1, ax2, ax3]) + } + + #[test] + fn t_get_mesh() { + let ax1 = AxisDescriptor::new(0.5, 1., 10); + let ax2 = AxisDescriptor::new(-std::f64::consts::PI, std::f64::consts::PI, 20); + let ax3 = AxisDescriptor::new(0., 2., 15); + let expected_step_x = 1. / 10.; //Cylindrical start ax from 0 to max_range + let mesh = get_mesh(MeshType::Cylindrical, [ax1, ax2, ax3]); + assert!( + mesh.mesh_step_axis(0) == expected_step_x, + "{} {}", + mesh.mesh_step_axis(0), + expected_step_x + ); + } + + #[test] + fn t_getter() { + let mesh = ref_mesh_cyclindrical(); + assert!(mesh.max_axis(0) == MAX_AX1); + assert!(mesh.max_axis(1) == std::f64::consts::PI); + assert!(mesh.max_axis(2) == MAX_AX3); + + assert!(mesh.min_axis(0) == 0.); + assert!(mesh.min_axis(1) == -std::f64::consts::PI); + + assert!(mesh.n_points_axis(0) == NUMBER_POINT_AX1); + assert!(mesh.n_points_axis(1) == NUMBER_POINT_AX2); + assert!(mesh.n_points_axis(2) == NUMBER_POINT_AX3); + assert!(mesh.number_cell() == NUMBER_POINT_AX1 * NUMBER_POINT_AX2 * NUMBER_POINT_AX3); + } + + #[test] + fn t_cell_surface_cylindrical() { + let ax1 = AxisDescriptor::new(0., 4., 10); + let ax2 = AxisDescriptor::new(-std::f64::consts::PI, std::f64::consts::PI, 10); + let ax3 = AxisDescriptor::new(0., 2., 10); + + let mesh = get_mesh(MeshType::Cylindrical, [ax1, ax2, ax3]); + + let r_face_center = 0.4; + let dtheta = 2. * std::f64::consts::PI / 10.; + let dz = 2. / 10.; + let expected_surface = r_face_center * dtheta * dz; + let actual_surface = mesh.cell_surface(0, OrientedAxis::I); + assert!( + (actual_surface - expected_surface).abs() < 1e-10, + "Radial face surface incorrect: got {}, expected {}", + actual_surface, + expected_surface + ); + + let dr = 4. / 10.; // 0.4 + let expected_surface = dr * dz; + let actual_surface = mesh.cell_surface(0, OrientedAxis::J); + assert!( + (actual_surface - expected_surface).abs() < 1e-10, + "Theta face surface incorrect: got {}, expected {}", + actual_surface, + expected_surface + ); + + let r1 = 0.0; + let r2 = 0.4; + let dtheta = 2. * std::f64::consts::PI / 10.; + + let expected_surface = 0.5 * (r2 * r2 - r1 * r1) * dtheta; + let actual_surface = mesh.cell_surface(0, OrientedAxis::K); + assert!( + (actual_surface - expected_surface).abs() < 1e-10, + "Axial face surface incorrect: got {}, expected {}", + actual_surface, + expected_surface + ); + } + + #[test] + fn t_cell_volume_cylindrical() { + let ax1 = AxisDescriptor::new(0., 4., 10); + let ax2 = AxisDescriptor::new(-std::f64::consts::PI, std::f64::consts::PI, 10); + let ax3 = AxisDescriptor::new(0., 2., 10); + let mesh = get_mesh(MeshType::Cylindrical, [ax1, ax2, ax3]); + + let r1 = 0.0; + let r2 = 0.4; + let dtheta = 2. * std::f64::consts::PI / 10.; + let dz = 2.0 / 10.0; + + let expected_volume = 0.5 * (r2 * r2 - r1 * r1) * dtheta * dz; + let actual_volume = mesh.cell_volume(0); + assert!( + (expected_volume - actual_volume).abs() < 1e-10, + "Axial face volume incorrect: got {}, expected {}", + actual_volume, + expected_volume + ); + + let expected_full_volume = std::f64::consts::PI * 4. * 4. * 2.; + let full_volume: f64 = (0..mesh.number_cell()).map(|e| mesh.cell_volume(e)).sum(); + assert!( + (expected_full_volume - full_volume).abs() < 1e-10, + "full_volume incorrect: got {}, expected {}", + full_volume, + expected_full_volume + ); + } + + #[test] + fn t_identification_cylindrical() { + let mesh = ref_mesh_cyclindrical(); + + let assert_id = |a: Coords3, expect: usize| { + let CartesianCoordinates(aa) = CylindricalCoordinates(a).into(); + + let id1 = mesh + .cell_from_coordinates(&aa) + .expect("Test neighbors: coordinates for cell a are outside the mesh."); + + assert!( + id1 == expect, + "Assertion failed: expected {:?}, got {:?}", + expect, + id1 + ); + }; + + assert_id([0., -std::f64::consts::PI, 0.], 0); + + assert_id([0., -std::f64::consts::PI, MAX_AX3], NUMBER_POINT_AX3 - 1); + let theta = -std::f64::consts::PI + mesh.mesh_step_axis(1) * 1.1; + //R!=0 because with cartesian conversion is x=rcos(theta) if theta changes but no r its the same compartment + assert_id([0.01, theta, 0.], NUMBER_POINT_AX3); + //-1 because we consider cell ID for 0 to n-1 + assert_id( + [MAX_AX1, std::f64::consts::PI, MAX_AX3], + (NUMBER_POINT_AX3 * NUMBER_POINT_AX1 * NUMBER_POINT_AX2) - 1, + ); + } + + #[test] + fn t_boundary_cylindrical() { + let ax1 = AxisDescriptor::new(0., MAX_AX1, 5); + let ax2 = AxisDescriptor::new(-std::f64::consts::PI, std::f64::consts::PI, 10); + let ax3 = AxisDescriptor::new(0., MAX_AX3, 10); + let mesh = get_mesh(MeshType::Cylindrical, [ax1, ax2, ax3]); + + let mut v = mesh.get_boundary(); + let w = [ + 0, 9, 10, 19, 20, 29, 30, 39, 40, 49, 50, 59, 60, 69, 70, 79, 80, 89, 90, 99, 100, 109, + 110, 119, 120, 129, 130, 139, 140, 149, 150, 159, 160, 169, 170, 179, 180, 189, 190, + 199, 200, 209, 210, 219, 220, 229, 230, 239, 240, 249, 250, 259, 260, 269, 270, 279, + 280, 289, 290, 299, 300, 309, 310, 319, 320, 329, 330, 339, 340, 349, 350, 359, 360, + 369, 370, 379, 380, 389, 390, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, + 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, + 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, + 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, + 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, + 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, + 495, 496, 497, 498, 499, + ]; + v.sort(); + assert_eq!(v, w); + } + + #[test] + fn test_theta_plane_neighbors() { + let (mesh, step_r, step_theta, step_z) = + utils_planes_neighbors(NUMBER_POINT_AX1, NUMBER_POINT_AX2, NUMBER_POINT_AX3); + let eps_step = 0.1; + let aa = [0.1, -std::f64::consts::PI, 0.0]; + let bb = [0.1, -std::f64::consts::PI + step_theta + eps_step, 0.0]; + let CartesianCoordinates(aa) = CylindricalCoordinates(aa).into(); + let CartesianCoordinates(bb) = CylindricalCoordinates(bb).into(); + let cell1_id = mesh.cell_from_coordinates(&aa).unwrap(); + let cell2_id = mesh.cell_from_coordinates(&bb).unwrap(); + + assert!(cell1_id != cell2_id); + + let (plane, axis) = mesh.get_interface_plane(cell1_id, cell2_id); + assert_eq!(axis, 1); + assert!((plane.extent_u[0] - 0.0).abs() < 1e-8); // r min + assert!((plane.extent_u[1] - step_r).abs() < 1e-8); // r max + assert!((plane.extent_v[0] - 0.0).abs() < 1e-8); // z min + assert!((plane.extent_v[1] - step_z).abs() < 1e-8); // z max + // assert!(plane.origin[0]==mesh.ge + } + + #[test] + fn test_r_plane_neighbors() { + let (mesh, step_r, step_theta, step_z) = + utils_planes_neighbors(NUMBER_POINT_AX1, NUMBER_POINT_AX2, NUMBER_POINT_AX3); + let eps_step = 0.1; + let aa = [0.0, 0.0, 0.0]; + let bb = [step_r + eps_step, 0.0, 0.0]; + + let CartesianCoordinates(aa) = CylindricalCoordinates(aa).into(); + let CartesianCoordinates(bb) = CylindricalCoordinates(bb).into(); + + let cell1_id = mesh.cell_from_coordinates(&aa).unwrap(); + let cell2_id = mesh.cell_from_coordinates(&bb).unwrap(); + + assert!(cell1_id != cell2_id); + + let m = mesh.cell_points(cell1_id); + let mp = mesh.get_cell_edge(1, m[1]); + let mn = mesh.get_cell_edge(1, m[1] + 1); + + let (plane, axis) = mesh.get_interface_plane(cell1_id, cell2_id); + assert_eq!(axis, 0); + assert!( + (plane.extent_u[0] - mp).abs() < 1e-8, + "{} vs {}", + plane.extent_u[0], + step_theta + ); + assert!( + (plane.extent_u[1] - mn).abs() < 1e-8, + "{} vs {}", + plane.extent_u[1], + 0. + ); + + assert!((plane.extent_v[0] - 0.0).abs() < 1e-8); + assert!((plane.extent_v[1] - step_z).abs() < 1e-8); + } + + #[test] + fn fuzz_z_plane_neighbors() { + let nax1 = [5, 7, 10]; + let nax2 = [5, 7, 10]; + let nax3 = [5, 7, 10]; + + for &n1 in nax1.iter() { + for &n2 in nax2.iter() { + for &n3 in nax3.iter() { + eprintln!("{} {} {}", n1, n2, n3); + test_z_plane_neighbors(n1, n2, n3); + } + } + } + } + + fn test_z_plane_neighbors(nax1: usize, nax2: usize, nax3: usize) { + let (mesh, step_r, step_theta, step_z) = utils_planes_neighbors(nax1, nax2, nax3); + let eps_step = 0.1; + let aa = [0.0, 0.0, 0.0]; + let bb = [0., 0.0, step_z + eps_step]; + + let CartesianCoordinates(aa) = CylindricalCoordinates(aa).into(); + let CartesianCoordinates(bb) = CylindricalCoordinates(bb).into(); + + let cell1_id = mesh.cell_from_coordinates(&aa).unwrap(); + let cell2_id = mesh.cell_from_coordinates(&bb).unwrap(); + + assert!(cell1_id != cell2_id); + + let (plane, axis) = mesh.get_interface_plane(cell1_id, cell2_id); + assert_eq!(axis, 2); + let m = mesh.cell_points(cell1_id); + let mp = mesh.get_cell_edge(1, m[1]); + let mn = mesh.get_cell_edge(1, m[1] + 1); + let theta = aa[1]; + let theta_ext = theta - step_theta / 2.; + let theta_in = theta + step_theta / 2.; + + // let mut origin: Vec = (0..3).map(|i| mesh.get_cell_center(i, m[i])).collect(); + // origin[2] = step_z; + + // assert!( + // origin == plane.origin.0, + // "{:?} {:?}", + // origin, + // plane.origin.0 + // ); + + assert!( + (plane.extent_v[0] - mp).abs() < 1e-8, + "1 {} expected {}", + plane.extent_v[0], + theta_ext + ); + assert!( + (plane.extent_v[1] - mn).abs() < 1e-8, + "2 {} expected {}", + theta_in, + 0. + ); + + assert!( + (plane.extent_u[0] - 0.0).abs() < 1e-8, + "3 {} expected {}", + plane.extent_u[0], + 0. + ); + assert!( + (plane.extent_u[1] - step_r).abs() < 1e-8, + "4 {} expected {}", + plane.extent_u[1], + step_z + ); + } + + #[test] + fn t_neighbors_cylindrical() { + use NeighborDirection::*; + + let mesh = ref_mesh_cyclindrical(); + + let assert_neighbors = + |a: CylindricalCoordinates, b: CylindricalCoordinates, expected: NeighborDirection| { + let CartesianCoordinates(aa) = a.into(); + + let CartesianCoordinates(bb) = b.into(); + + let id1 = mesh + .cell_from_coordinates(&aa) + .expect("Test neighbors: coordinates for cell a are outside the mesh."); + let id2 = mesh + .cell_from_coordinates(&bb) + .expect("Test neighbors: coordinates for cell b are outside the mesh."); + + let neighbors = mesh.are_cell_neighbor(id1, id2); + assert!( + neighbors == expected, + "Assertion failed: expected {:?}, got {:?}", + expected, + neighbors + ); + }; + + // Fixed coordinates and offsets for testing + let fix_i = 2.2; + let offset_i = mesh.mesh_step_axis(0); + let fix_j = 0.; + let offset_j = 0.68; + let fix_k = 4.0; + let offset_k = 0.9; + + // Assert neighbor relationships in different directions + + // Testing in X direction + assert_neighbors( + CylindricalCoordinates([fix_i, fix_j, fix_k]), + CylindricalCoordinates([fix_i + offset_i, fix_j, fix_k]), + XPlus, + ); + assert_neighbors( + CylindricalCoordinates([fix_i + offset_i, fix_j, fix_k]), + CylindricalCoordinates([fix_i, fix_j, fix_k]), + XMinus, + ); + + // Testing in Y direction + assert_neighbors( + CylindricalCoordinates([fix_i, fix_j + offset_j, fix_k]), + CylindricalCoordinates([fix_i, fix_j, fix_k]), + YMinus, + ); + assert_neighbors( + CylindricalCoordinates([fix_i, fix_j, fix_k]), + CylindricalCoordinates([fix_i, fix_j + offset_j, fix_k]), + YPlus, + ); + + // Testing in Z direction + assert_neighbors( + CylindricalCoordinates([fix_i, fix_j, fix_k + offset_k]), + CylindricalCoordinates([fix_i, fix_j, fix_k]), + ZMinus, + ); + assert_neighbors( + CylindricalCoordinates([fix_i, fix_j, fix_k]), + CylindricalCoordinates([fix_i, fix_j, fix_k + offset_k]), + ZPlus, + ); + + // Testing non-neighbor cases + assert_neighbors( + CylindricalCoordinates([0., fix_j, fix_k + offset_k]), + CylindricalCoordinates([fix_i, fix_j, fix_k]), + NotNeighbors, + ); + assert_neighbors( + CylindricalCoordinates([fix_i, fix_j, fix_k]), + CylindricalCoordinates([0., fix_j, fix_k + offset_k]), + NotNeighbors, + ); + let little_offset = mesh.mesh_step_axis(0) * 1.005; + let c1: f64 = mesh.get_cell_edge(0, 2); + assert_neighbors( + CylindricalCoordinates([c1, fix_j, fix_k]), + CylindricalCoordinates([c1 + little_offset, fix_j, fix_k]), + NotNeighbors, + ); + } +} diff --git a/cmtool-core/src/model/compartments.rs b/cmtool-core/src/model/compartments.rs index 7ee88bc4..8a9b9c5b 100644 --- a/cmtool-core/src/model/compartments.rs +++ b/cmtool-core/src/model/compartments.rs @@ -44,10 +44,9 @@ impl CompartmentInfo { let mut tmp_count_k_element: Vec = vec![0; geometry.n_zone()]; - for (volume_element_global_id, n_compartment_in_velem) in + for (volume_element_global_id, &n_compartment_in_velem) in geometry.volume_elements.enumerate_number_id() { - let n_compartment_in_velem = *n_compartment_in_velem; let (elem_type, n_vertex) = geometry .volume_elements .get_element_and_nvertex(volume_element_global_id); @@ -66,7 +65,7 @@ impl CompartmentInfo { let volume = compute_volume(&local_vertices, elem_type).unwrap() / (n_compartment_in_velem as f64); - assert!(volume >= 0.); + assert!(volume >= 0., "CMTOOL(\"fill\"): Negative volume"); self.volumes[compartment_id][k_element] = ElementVolumeInfo { global_id: volume_element_global_id, volume, diff --git a/cmtool-core/src/model/geometry.rs b/cmtool-core/src/model/geometry.rs index f3ef3c31..c0aa69cf 100644 --- a/cmtool-core/src/model/geometry.rs +++ b/cmtool-core/src/model/geometry.rs @@ -200,62 +200,25 @@ impl CMGeometry { let mut count = CountVolumeElement::new(self.n_zone()); let grid = self.grid.as_ref().unwrap(); - // for (_gid, interface_cid_0, interface_cid_k, k_vertex) in self.interface_iter() { - // count.incr_compartment(interface_cid_k); - // if k_vertex >= 1 { - // let neighbors = grid.are_cell_neighbor(interface_cid_0, interface_cid_k); - - // if neighbors != NeighborDirection::NotNeighbors { - // let (id1, id2) = neighbors.ordered_pair(interface_cid_0, interface_cid_k); - - // count.incr_interface(id1, id2); - // } - // } - // } - // for (vol_element_global_id, _, interface_cid_k, k_vertex) in self.interface_iter() { count.incr_compartment(interface_cid_k); - if k_vertex >= 1 { - for i in 0..k_vertex { - let cid_i = self - .volume_elements - .get_list_compartment_id(vol_element_global_id, i); - let neighbors = grid.are_cell_neighbor(cid_i, interface_cid_k); - if neighbors != NeighborDirection::NotNeighbors { - let (id1, id2) = neighbors.ordered_pair(cid_i, interface_cid_k); - count.incr_interface(id1, id2); - } + // if k_vertex >= 1 { + for i in 0..k_vertex { + let cid_i = self + .volume_elements + .get_list_compartment_id(vol_element_global_id, i); + let neighbors = grid.are_cell_neighbor(cid_i, interface_cid_k); + if neighbors != NeighborDirection::NotNeighbors { + let (id1, id2) = neighbors.ordered_pair(cid_i, interface_cid_k); + count.incr_interface(id1, id2); } } + // } } count } - // pub fn get_count_volume_element_first_pass(&self) -> CountVolumeElement { - // let mut count = CountVolumeElement::new(self.n_zone()); - - // let grid = self.grid.as_ref().unwrap(); - - // let mut seen_interfaces = std::collections::HashSet::new(); - - // for (_, interface_cid_0, interface_cid_k, _) in self.interface_iter() { - // count.incr_compartment(interface_cid_k); - // count.incr_compartment(interface_cid_0); - // if interface_cid_0 != interface_cid_k { - // let neighbors = grid.are_cell_neighbor(interface_cid_0, interface_cid_k); - // if neighbors != NeighborDirection::NotNeighbors { - // let (id1, id2) = neighbors.ordered_pair(interface_cid_0, interface_cid_k); - // if !seen_interfaces.contains(&(id1, id2)) { - // seen_interfaces.insert((id1, id2)); - // count.incr_interface(id1, id2); - // } - // } - // } - // } - - // count - // } pub fn init( n_div: [usize; 3], diff --git a/cmtool-core/src/model/interfaces.rs b/cmtool-core/src/model/interfaces.rs index bfb539a9..ed91a907 100644 --- a/cmtool-core/src/model/interfaces.rs +++ b/cmtool-core/src/model/interfaces.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later use crate::coordinates::*; -use crate::grid::NeighborDirection; +use crate::grid::{NeighborDirection, index_to_oriented}; use crate::model::CMGeometry; use crate::utils::compute_intersection_area; @@ -112,6 +112,58 @@ impl AInterfacesInfo { self.global_id_from_interface = self.count_interfaces_second_pass(geometry, &interfaces_id_from_cells); self.fill_area(geometry, &planes); + + // for i_interface in 0..self.n_facet.len() { + // let total: f64 = self.area[i_interface].iter().sum(); + // if total > 0.0 { + // let source = self.ids[i_interface].source_id; + // let axis = self.normal_axis[i_interface]; + // let theoretical = grid.cell_surface(source, index_to_oriented(axis)); + // let factor = theoretical / total; + // for a in self.area[i_interface].iter_mut() { + // *a *= factor; + // } + // } + // } + + // self.check_areas(geometry); + } + + fn check_areas(&self, geometry: &CMGeometry) { + let grid = geometry.get_grid().unwrap(); + let n_zones = geometry.n_zone(); + for cell_id in 0..n_zones { + for axis_idx in 0..3 { + let axis = crate::grid::index_to_oriented(axis_idx); + let theoretical_area = grid.cell_surface(cell_id, axis); + let total_area: f64 = self + .ids + .iter() + .enumerate() + .filter(|(i, id)| { + (id.source_id == cell_id || id.target_id == cell_id) + && self.normal_axis[*i] == axis_idx + }) + .map(|(i, _)| self.area[i].iter().sum::()) + .sum(); + + if ((total_area - theoretical_area).abs() / theoretical_area) < 0.1 { + println!( + "(areas): area incorect : axis: {}\r\n -cell_id:{}\r\n -total_area: {}\r\n -theoretical: {}", + axis_idx, cell_id, total_area, theoretical_area + ); + } + + // assert!( + // ((total_area - theoretical_area).abs() / theoretical_area) < 0.1, + // "RCMTOOL(areas): area incorect : axis: {}\r\n -cell_id:{}\r\n -total_area: {}\r\n -theoretical: {}", + // axis_idx, + // cell_id, + // total_area, + // theoretical_area + // ); + } + } } fn count_interfaces_second_pass( @@ -128,37 +180,23 @@ impl AInterfacesInfo { let grid = geometry.get_grid().unwrap(); let n_zones = geometry.n_zone(); - // for (vol_element_global_id, interface_cid_0, interface_cid_k, k_vertex) in - // geometry.interface_iter() - // { - // if k_vertex >= 1 - // && grid.are_cell_neighbor(interface_cid_0, interface_cid_k) - // != NeighborDirection::NotNeighbors - // { - // let interface_global_id = - // interfaces_id_from_cells[interface_cid_0 * n_zones + interface_cid_k]; - // let k_element = tmp_element_counter[interface_global_id]; - // tmp_element_counter[interface_global_id] += 1; - // global_id_from_interface[interface_global_id][k_element] = vol_element_global_id; - // } - // } for (vol_element_global_id, _, interface_cid_k, k_vertex) in geometry.interface_iter() { - if k_vertex >= 1 { - for i in 0..k_vertex { - let cid_i = geometry - .volume_elements - .get_list_compartment_id(vol_element_global_id, i); - let neighbors = grid.are_cell_neighbor(cid_i, interface_cid_k); - if neighbors != NeighborDirection::NotNeighbors { - let interface_global_id = - interfaces_id_from_cells[cid_i * n_zones + interface_cid_k]; - let k_element = tmp_element_counter[interface_global_id]; - tmp_element_counter[interface_global_id] += 1; - global_id_from_interface[interface_global_id][k_element] = - vol_element_global_id; - } + // if k_vertex >= 1 { + for i in 0..k_vertex { + let cid_i = geometry + .volume_elements + .get_list_compartment_id(vol_element_global_id, i); + let neighbors = grid.are_cell_neighbor(cid_i, interface_cid_k); + if neighbors != NeighborDirection::NotNeighbors { + let interface_global_id = + interfaces_id_from_cells[cid_i * n_zones + interface_cid_k]; + let k_element = tmp_element_counter[interface_global_id]; + tmp_element_counter[interface_global_id] += 1; + global_id_from_interface[interface_global_id][k_element] = + vol_element_global_id; } } + // } } global_id_from_interface diff --git a/cmtool-core/src/model/mod.rs b/cmtool-core/src/model/mod.rs index 5efe623a..dc0ad4f1 100644 --- a/cmtool-core/src/model/mod.rs +++ b/cmtool-core/src/model/mod.rs @@ -49,34 +49,26 @@ impl CMModel { geometry.n_zone() ) } + let interface_count_raw = volume_element_count.at_interface.clone(); let (c_info, interfaces) = volume_element_count.into_reduce(); - let n_max_interface = geometry.get_grid().as_ref().unwrap().n_maximum_interface(); - - if interfaces.n_interfaces() >= n_max_interface { + if interfaces.n_interfaces() >= geometry.get_grid().as_ref().unwrap().n_maximum_interface() + { unimplemented!( - "should have intefaces < n_maximum_interface {} {}", + "RCMTOOL: CMModel::init: should have intefaces < n_maximum_interface {} {}", interfaces.n_interfaces(), - n_max_interface + geometry.get_grid().as_ref().unwrap().n_maximum_interface() ) } - // if interfaces.n_facet.len() != n_max_interface { - // eprintln!( - // "Intefaces should be n_maximum_interface {} {}", - // interfaces.n_facet.len(), - // n_max_interface - // ); - // // unimplemented!("Intefaces should be n_maximum_interface") - // } - let mut model = Self { geometry, c_info, interfaces, }; + model.c_info.fill(&model.geometry); model.interfaces.fill(&model.geometry, &interface_count_raw); @@ -98,54 +90,99 @@ impl CMModel { let mut flows: Vec = vec![Default::default(); n_fluxes]; + // for (i_interface, flow) in flows.iter_mut().enumerate() { + // let axis: usize = self.interfaces.normal_axis[i_interface]; + // let current_interface_area = &self.interfaces.area[i_interface]; + // let curent_inteface_element = &self.interfaces.global_id_from_interface[i_interface]; + + // for (global_id, area) in curent_inteface_element.iter().zip(current_interface_area) { + // let vector_value = CartesianVec3(vector.get_slice_xyz(*global_id).to_owned()); + + // let coords = match self.geometry.mesh_type { + // MeshType::Cylindrical => { + // let CartesianCoordinates(centroid) = + // self.geometry.volume_elements.xyz[*global_id]; + + // let CylindricalCoordinates(centroid) = + // CartesianCoordinates(centroid).into(); + + // vector_value.to_cylindrical_vec(centroid[1]).0 + + // // let cyl_vec = vector_value + // // .to_cylindrical_vec(self.interfaces.interface_theta[i_interface]) + // // .0; + // // cyl_vec + // // + // // + // // + // // let r = (centroid[0].powi(2) + centroid[1].powi(2)).sqrt(); + // // match axis { + // // 1 => [cyl_vec[0], cyl_vec[1] * r, cyl_vec[2]], + // // _ => cyl_vec, + // // } + // } + // _ => vector_value.0, + // }; + + // let f = coords[axis] * area; + + // if f > 0. { + // flow.source_flow += f + // } else if f < 0. { + // flow.target_flow += f.abs() + // } + // } + // } + for (i_interface, flow) in flows.iter_mut().enumerate() { let axis: usize = self.interfaces.normal_axis[i_interface]; let current_interface_area = &self.interfaces.area[i_interface]; let curent_inteface_element = &self.interfaces.global_id_from_interface[i_interface]; - for (global_id, area) in curent_inteface_element.iter().zip(current_interface_area) { - let vector_value = CartesianVec3(vector.get_slice_xyz(*global_id).to_owned()); - - let coords = match self.geometry.mesh_type { - MeshType::Cylindrical => { - let CartesianCoordinates(centroid) = - self.geometry.volume_elements.xyz[*global_id]; - - let CylindricalCoordinates(centroid) = - CartesianCoordinates(centroid).into(); - - let cyl_vec = vector_value - .to_cylindrical_vec(self.interfaces.interface_theta[i_interface]) - .0; - - let r = (centroid[0].powi(2) + centroid[1].powi(2)).sqrt(); - match axis { - 1 => [cyl_vec[0], cyl_vec[1] * r, cyl_vec[2]], - _ => cyl_vec, - } - } - _ => vector_value.0, - }; - - let f = coords[axis] * area; - - if f > 0. { - flow.source_flow += f - } else if f < 0. { - flow.target_flow += f.abs() - } + let total_area: f64 = current_interface_area.iter().sum(); + let v_avg = curent_inteface_element + .iter() + .zip(current_interface_area) + .fold([0.0f64; 3], |acc, (gid, a)| { + let v = CartesianVec3(vector.get_slice_xyz(*gid).to_owned()); + let CartesianCoordinates(centroid) = self.geometry.volume_elements.xyz[*gid]; + let theta = centroid[1].atan2(centroid[0]); + let cyl = v.to_cylindrical_vec(theta).0; + [ + acc[0] + cyl[0] * a / total_area, + acc[1] + cyl[1] * a / total_area, + acc[2] + cyl[2] * a / total_area, + ] + }); + + let coords = v_avg; + + let source_id = self.interfaces.ids[i_interface].source_id; + let axis_oriented = crate::grid::index_to_oriented(axis); + let theoretical_area = self + .geometry + .get_grid() + .unwrap() + .cell_surface(source_id, axis_oriented); + + let f = coords[axis] * theoretical_area; + + if f > 0. { + flow.source_flow += f + } else if f < 0. { + flow.target_flow += f.abs() } } + self.clean(&mut flows); + for (i_interface, rd) in flux_field.fluxes.iter_mut().enumerate() { let InterfaceInfo { source_id, target_id, } = self.interfaces.ids[i_interface]; - rd.id_source = source_id as u32; rd.id_target = target_id as u32; - let InterfaceFlow { source_flow, target_flow, @@ -154,6 +191,23 @@ impl CMModel { rd.flux_target_source = *target_flow; } + // for (i_interface, rd) in flux_field.fluxes.iter_mut().enumerate() { + // let InterfaceInfo { + // source_id, + // target_id, + // } = self.interfaces.ids[i_interface]; + + // rd.id_source = source_id as u32; + // rd.id_target = target_id as u32; + + // let InterfaceFlow { + // source_flow, + // target_flow, + // } = &flows[i_interface]; + // rd.flux_source_target = *source_flow; + // rd.flux_target_source = *target_flow; + // } + Ok(flux_field) } @@ -161,6 +215,91 @@ impl CMModel { // todo!() // } + fn clean(&self, flows: &mut [InterfaceFlow]) { + const TOL: f64 = 1e-19; + const TOL_CONV: f64 = 1e-12; + const MAX_IT: usize = 10000; + let mut balance = vec![0.0f64; self.geometry.n_zone()]; + let n_interfaces_per_zone = { + let mut tmp = vec![0usize; self.geometry.n_zone()]; + for i_interface in 0..flows.len() { + let source_id = self.interfaces.ids[i_interface].source_id; + let target_id = self.interfaces.ids[i_interface].target_id; + tmp[source_id] += 1; + tmp[target_id] += 1; + } + tmp + }; + + // let f_err = |_balance: &mut [f64], _flows: &[InterfaceFlow]| { + // for (i_interface, flow) in _flows.iter().enumerate() { + // let source_id = self.interfaces.ids[i_interface].source_id; + // let target_id = self.interfaces.ids[i_interface].target_id; + // _balance[source_id] += flow.target_flow - flow.source_flow; + // _balance[target_id] += flow.source_flow - flow.target_flow; + // } + // _balance.iter().map(|x| x.abs()).fold(0.0f64, f64::max) + // }; + + let f_err = |_balance: &mut [f64], _flows: &[InterfaceFlow]| { + for (i_interface, flow) in _flows.iter().enumerate() { + let source_id = self.interfaces.ids[i_interface].source_id; + let target_id = self.interfaces.ids[i_interface].target_id; + _balance[source_id] += flow.target_flow - flow.source_flow; + _balance[target_id] += flow.source_flow - flow.target_flow; + } + let total_balance = _balance.iter().map(|&x| x.abs()).sum::(); + let total_flow = _flows + .iter() + .map(|flow| flow.target_flow + flow.source_flow) + .sum::(); + + total_balance / total_flow + }; + + let mut conv = false; + let mut it = 0; + let mut err = 0.; + + while it < MAX_IT && !conv { + balance.fill(0.0); + let max_err = f_err(&mut balance, flows); + if max_err < TOL { + break; + } + println!("{} {}", max_err, it); + conv = (err - max_err).abs() < TOL_CONV; + err = max_err; + it += 1; + for (i_interface, flow) in flows.iter_mut().enumerate() { + let source_id = self.interfaces.ids[i_interface].source_id; + let target_id = self.interfaces.ids[i_interface].target_id; + let corr = (balance[source_id] - balance[target_id]) + / (n_interfaces_per_zone[source_id] + n_interfaces_per_zone[target_id]) as f64; + flow.source_flow += corr; + flow.target_flow -= corr; + } + } + + // for i in 0..MAX_IT { + // balance.fill(0.0); + + // let max_err = f_err(&mut balance, flows); + + // println!("{} {}", max_err, i); + // if max_err < TOL { + // break; + // } + // for (i_interface, flow) in flows.iter_mut().enumerate() { + // let source_id = self.interfaces.ids[i_interface].source_id; + // let target_id = self.interfaces.ids[i_interface].target_id; + // let corr = (balance[source_id] - balance[target_id]) + // / (n_interfaces_per_zone[source_id] + n_interfaces_per_zone[target_id]) as f64; + // flow.source_flow += corr; + // flow.target_flow -= corr; + // } + // } + } pub fn export_volume_integral_per_zone( &self, model_scalar: Scalar, diff --git a/cmtool-core/src/utils/area.rs b/cmtool-core/src/utils/area.rs index ec24137a..32841388 100644 --- a/cmtool-core/src/utils/area.rs +++ b/cmtool-core/src/utils/area.rs @@ -26,14 +26,15 @@ fn sort_polygon_ccw(points: &[[f64; 2]]) -> Vec<[f64; 2]> { fn project_points_to_plane_2d(points: &[[f64; 3]], normal: &CartesianVec3) -> Vec<[f64; 2]> { let n = normal.normalized(); - let arbitrary = CartesianVec3::from_point_origin(if n.0[0].abs() < 0.9 { - CartesianCoordinates([1.0, 0.0, 0.0]) + let arbitrary = if n.0[0].abs() < n.0[2].abs() { + CartesianVec3([1.0, 0.0, 0.0]) } else { - CartesianCoordinates([0.0, 1.0, 0.0]) - }); - - let u = n.cross(&arbitrary).normalized(); - let v = n.cross(&u); + CartesianVec3([0.0, 0.0, 1.0]) + }; + // let u = n.cross(&arbitrary).normalized(); + // let v = u.cross(&n).normalized(); + let v = n.cross(&arbitrary).normalized(); + let u = v.cross(&n).normalized(); points .iter() @@ -42,40 +43,49 @@ fn project_points_to_plane_2d(points: &[[f64; 3]], normal: &CartesianVec3) -> Ve .collect() } +// fn sort_points_ccw_3d(points: &[[f64; 3]], normal: &CartesianVec3) -> Vec<[f64; 3]> { +// let projected = project_points_to_plane_2d(points, normal); +// let sorted_2d = sort_polygon_ccw(&projected); + +// let mut sorted_3d = Vec::with_capacity(points.len()); + +// for p2d in &sorted_2d { +// let idx = projected +// .iter() +// .enumerate() +// .min_by(|(_, a), (_, b)| { +// let da = (a[0] - p2d[0]).hypot(a[1] - p2d[1]); +// let db = (b[0] - p2d[0]).hypot(b[1] - p2d[1]); +// da.partial_cmp(&db).unwrap() +// }) +// .map(|(i, _)| i) +// .unwrap(); +// sorted_3d.push(points[idx]); +// } + +// sorted_3d +// } +// fn sort_points_ccw_3d(points: &[[f64; 3]], normal: &CartesianVec3) -> Vec<[f64; 3]> { let projected = project_points_to_plane_2d(points, normal); - let sorted_2d = sort_polygon_ccw(&projected); - - let mut sorted_3d = Vec::with_capacity(points.len()); - - for p2d in &sorted_2d { - let idx = projected - .iter() - .enumerate() - .min_by(|(_, a), (_, b)| { - let da = (a[0] - p2d[0]).hypot(a[1] - p2d[1]); - let db = (b[0] - p2d[0]).hypot(b[1] - p2d[1]); - da.partial_cmp(&db).unwrap() - }) - .map(|(i, _)| i) - .unwrap(); - sorted_3d.push(points[idx]); - } + let mut indices: Vec = (0..points.len()).collect(); + let centroid = { + let (mut sx, mut sy) = (0.0, 0.0); + for p in &projected { + sx += p[0]; + sy += p[1]; + } + [sx / projected.len() as f64, sy / projected.len() as f64] + }; + indices.sort_by(|&a, &b| { + let angle_a = (projected[a][1] - centroid[1]).atan2(projected[a][0] - centroid[0]); + let angle_b = (projected[b][1] - centroid[1]).atan2(projected[b][0] - centroid[0]); + angle_a.partial_cmp(&angle_b).unwrap() + }); - sorted_3d + indices.iter().map(|&i| points[i]).collect() } -// fn polygon_area_2d(points: &Vec<[f64; 2]>) -> f64 { -// let n = points.len(); -// let mut area = 0.0; -// for i in 0..n { -// let (x0, y0) = (points[i][0], points[i][1]); -// let (x1, y1) = (points[(i + 1) % n][0], points[(i + 1) % n][1]); -// area += x0 * y1 - x1 * y0; -// } -// area.abs() * 0.5 -// } - fn polygon_area_3d(points: &[Coords3], normal: &CartesianVec3) -> f64 { let n = normal.normalized(); @@ -93,86 +103,9 @@ fn polygon_area_3d(points: &[Coords3], normal: &CartesianVec3) -> f64 { 0.5 * (area_vec.dot(&n)).abs() } -// fn tetra_area(vertices: [CartesianCoordinates; 4], plane: &BoundedPlane) -> f64 { -// let mut intersection_points = vec![]; - -// let BoundedPlane { -// normal, -// origin: point, -// .. -// } = plane; - -// let d = -normal.dot(&CartesianVec3::from_point_origin(*point)); // plane offset -// let distances: Vec = vertices -// .iter() -// .map(|coords| CartesianVec3::from_point_origin(*coords)) -// .map(|v| normal.dot(&v) + d) -// .collect(); - -// let mut points_on_plane = vec![]; -// let edge_len = (0..4) -// .flat_map(|i| (i + 1..4).map(move |j| (i, j))) -// .map(|(i, j)| { -// let e = [ -// vertices[i].0[0] - vertices[j].0[0], -// vertices[i].0[1] - vertices[j].0[1], -// vertices[i].0[2] - vertices[j].0[2], -// ]; -// (e[0] * e[0] + e[1] * e[1] + e[2] * e[2]).sqrt() -// }) -// .fold(0.0_f64, f64::max); -// let tol = 1e-4 * edge_len.max(1.0); - -// for (i, dist) in distances.iter().enumerate() { -// if dist.abs() < tol { -// points_on_plane.push(vertices[i].0); -// } -// } - -// for i in 0..4 { -// for j in (i + 1)..4 { -// let d1 = distances[i]; -// let d2 = distances[j]; -// if d1.abs() < tol || d2.abs() < tol { -// continue; -// } -// if d1 * d2 < 0.0 { -// let t = d1 / (d1 - d2); -// let p1 = &vertices[i].0; -// let p2 = &vertices[j].0; -// let intersection = [ -// p1[0] + t * (p2[0] - p1[0]), -// p1[1] + t * (p2[1] - p1[1]), -// p1[2] + t * (p2[2] - p1[2]), -// ]; -// intersection_points.push(intersection); -// } -// } -// } -// intersection_points.extend(points_on_plane.iter().cloned()); - -// if intersection_points.len() < 3 { -// return 0.0; -// } - -// let filtered: Vec<_> = intersection_points -// .iter() -// .cloned() -// .filter(|p| plane.is_point_inside(CartesianCoordinates(*p))) -// .collect(); - -// if filtered.len() < 3 { -// return 0.0; -// } - -// let sorted = sort_points_ccw_3d(&intersection_points, normal); - -// //Compute area using shoelace formula -// // return polygon_area_2d(&sorted); -// polygon_area_3d(&sorted, normal) -// } - fn tetra_area(vertices: [CartesianCoordinates; 4], plane: &BoundedPlane) -> f64 { + const REL_TOL_DISTANCE: f64 = 1e-6; + const EPSILON: f64 = 1e-12; //f64::EPSILON let mut intersection_points = vec![]; let BoundedPlane { @@ -200,7 +133,10 @@ fn tetra_area(vertices: [CartesianCoordinates; 4], plane: &BoundedPlane) -> f64 (e[0] * e[0] + e[1] * e[1] + e[2] * e[2]).sqrt() }) .fold(0.0_f64, f64::max); - let tol = 1e-10 * edge_len.max(1.0); + if edge_len < EPSILON { + return 0.0; + } + let tol = REL_TOL_DISTANCE * edge_len; for (i, dist) in distances.iter().enumerate() { if dist.abs() < tol { @@ -216,7 +152,7 @@ fn tetra_area(vertices: [CartesianCoordinates; 4], plane: &BoundedPlane) -> f64 continue; } if d1 * d2 < 0.0 { - let t = d1.abs() / (d1.abs() + d2.abs()); // Correction ici: formule plus stable + let t = d1.abs() / (d1.abs() + d2.abs()); let p1 = &vertices[i].0; let p2 = &vertices[j].0; let intersection = [ @@ -226,9 +162,37 @@ fn tetra_area(vertices: [CartesianCoordinates; 4], plane: &BoundedPlane) -> f64 ]; intersection_points.push(intersection); } + // if d1 * d2 < 0.0 { + // let t = d1.abs() / (d1.abs() + d2.abs()); + // let p1 = &vertices[i].0; + // let p2 = &vertices[j].0; + // let r1 = (p1[0].powi(2) + p1[1].powi(2)).sqrt(); + // let r2 = (p2[0].powi(2) + p2[1].powi(2)).sqrt(); + // let theta1 = p1[1].atan2(p1[0]); + // let theta2 = p2[1].atan2(p2[0]); + // let r_int = r1 + t * (r2 - r1); + // let theta_int = theta1 + t * (theta2 - theta1); + // intersection_points.push([ + // r_int * theta_int.cos(), + // r_int * theta_int.sin(), + // p1[2] + t * (p2[2] - p1[2]), + // ]); + // } + } + } + + for p in &points_on_plane { + let already_present = intersection_points.iter().any(|q| { + let dx = q[0] - p[0]; + let dy = q[1] - p[1]; + let dz = q[2] - p[2]; + (dx * dx + dy * dy + dz * dz).sqrt() < tol + }); + if !already_present { + intersection_points.push(*p); } } - intersection_points.extend(points_on_plane.iter().cloned()); + // intersection_points.extend(points_on_plane.iter().cloned()); if intersection_points.len() < 3 { return 0.0; @@ -243,8 +207,8 @@ fn tetra_area(vertices: [CartesianCoordinates; 4], plane: &BoundedPlane) -> f64 if filtered.len() < 3 { return 0.0; } - - let sorted = sort_points_ccw_3d(&intersection_points, normal); + // let sorted = sort_points_ccw_3d(&intersection_points, normal); + let sorted = sort_points_ccw_3d(&filtered, normal); polygon_area_3d(&sorted, normal) } @@ -274,7 +238,11 @@ pub fn compute_intersection_area( #[cfg(test)] mod test { use super::*; - fn make_bounded_plane(normal: CartesianVec3, origin: CartesianCoordinates) -> BoundedPlane { + fn make_bounded_plane( + normal: CartesianVec3, + origin: CartesianCoordinates, + axis: usize, + ) -> BoundedPlane { // let (u, v) = orthonormal_basis(&normal); let extent = [-10.0, 10.0]; @@ -284,7 +252,7 @@ mod test { origin, extent_u: extent, extent_v: extent, - axis: 0, + axis, } } @@ -308,7 +276,7 @@ mod test { let expected_area = 0.5 * (cross_prod.0[0].powi(2) + cross_prod.0[1].powi(2) + cross_prod.0[2].powi(2)).sqrt(); - let plane = make_bounded_plane(normal, a); + let plane = make_bounded_plane(normal, a, 0); // let area = tetra_area([a, b, c, d], plane); @@ -334,7 +302,7 @@ mod test { let normal = CartesianVec3([0., 0., 1.]); let point = CartesianCoordinates([0., 0., 0.5]); - let plane = make_bounded_plane(normal, point); + let plane = make_bounded_plane(normal, point, 2); // Calculate the intersection area // let area = tetra_area( @@ -378,4 +346,14 @@ mod test { area ); } + + #[test] + fn test_sort_points_ccw_3d() { + let normal = CartesianVec3([0., 0., 1.]); + let points = vec![[0.5, 0.0, 0.5], [0.0, 0.5, 0.5], [0.0, 0.0, 0.5]]; + let sorted = sort_points_ccw_3d(&points, &normal); + assert_eq!(sorted[0], [0.0, 0.0, 0.5]); + assert_eq!(sorted[1], [0.5, 0.0, 0.5]); + assert_eq!(sorted[2], [0.0, 0.5, 0.5]); + } } diff --git a/cmtool/src/main.rs b/cmtool/src/main.rs index 10e7ff28..5049acb0 100644 --- a/cmtool/src/main.rs +++ b/cmtool/src/main.rs @@ -58,12 +58,12 @@ fn auto_main(common: CommonArgs, autoargs: AutoArgs) -> Result<(), CmtoolError> handle.dump_real_volume(format!("{}/{}/vofL", root_dir, stem))?; handle.dump_real_volume(format!("{}/{}/vtot", root_dir, stem))?; - handle.dump_vector_from_scalar( - format!("{}/{}/flowL", root_dir, stem), - "/tmp/sanofi/inputs/RESULTS.scl1", - "/tmp/sanofi/inputs/RESULTS.scl2", - "/tmp/sanofi/inputs/RESULTS.scl3", - )?; + // handle.dump_vector_from_scalar( + // format!("{}/{}/flowL", root_dir, stem), + // "/tmp/sanofi/inputs/RESULTS.scl1", + // "/tmp/sanofi/inputs/RESULTS.scl2", + // "/tmp/sanofi/inputs/RESULTS.scl3", + // )?; #[cfg(feature = "use_vtk")] handle.write_vtk(format!("{}/{}/cma_case.vtu", root_dir, stem)); @@ -73,7 +73,7 @@ fn auto_main(common: CommonArgs, autoargs: AutoArgs) -> Result<(), CmtoolError> &RawDataFlux::read_raw("./out/cuve_sldmsh_initmrf/velocity.raw").unwrap(), ) .unwrap(); - println!("{}", f); + // println!("{}", f); // std::fs::write("/tmp/checks.csv", f); // // let f = cmtool::divergence_free( diff --git a/examples/python/mixing_simple.py b/examples/python/mixing_simple.py index e15c2444..d8e5d0f8 100644 --- a/examples/python/mixing_simple.py +++ b/examples/python/mixing_simple.py @@ -135,7 +135,7 @@ def check_mixing(fmt, final_time: float): if __name__ == "__main__": - final_time = 100 + final_time = 20 root = os.environ["EXAMPLE_ROOT"] # Let CMTool read and load the full case automatically, ready to iterate From db2740f3b2d52fe36acea6ab27bc6912951f74ae Mon Sep 17 00:00:00 2001 From: bcasale Date: Wed, 25 Mar 2026 10:37:17 +0100 Subject: [PATCH 39/44] feat(pycmtool): improve example and expose rawdata getter/setters --- cmtool-python/src/lib.rs | 3 +- cmtool-python/src/rd.rs | 58 +++++++++++++++++++++++++++ examples/python/complete_reactive.py | 10 +++-- examples/python/generate_reference.py | 56 ++++++++++++++++++++++++++ examples/python/mixing_simple.py | 8 +++- examples/python/reactive_mixing.py | 36 +++++++++-------- 6 files changed, 150 insertions(+), 21 deletions(-) create mode 100644 examples/python/generate_reference.py diff --git a/cmtool-python/src/lib.rs b/cmtool-python/src/lib.rs index a732aa27..d1bba5e4 100644 --- a/cmtool-python/src/lib.rs +++ b/cmtool-python/src/lib.rs @@ -37,7 +37,8 @@ mod pycmtool { #[pymodule_export] use super::{ DiscontinuousTransitionerWrapper, IterationStateWrapper, get_transitioner, - read_flowmap, read_rawflow, read_rawscalar, scalar_from_data, + new_raw_flux, read_flowmap, read_rawflow, read_rawscalar, scalar_from_data, + vector_from_data, }; } diff --git a/cmtool-python/src/rd.rs b/cmtool-python/src/rd.rs index c27b823b..65ed6831 100644 --- a/cmtool-python/src/rd.rs +++ b/cmtool-python/src/rd.rs @@ -1,5 +1,8 @@ +use cmtool_data::FluxFileHeader; use cmtool_data::RawData; +use cmtool_data::RawDataFlux; use cmtool_data::RawDataScalar; +use cmtool_data::RawFlux; use numpy::PyArray1; use numpy::PyArray2; use numpy::PyUntypedArrayMethods; @@ -76,14 +79,43 @@ pub fn scalar_from_data<'py>( /* Flows */ +#[pyclass(from_py_object, name = "RawFlux")] +#[derive(Clone, Copy)] +pub struct RawFluxWrapper(cmtool_data::RawFlux); + +#[pyfunction] +pub fn new_raw_flux( + _py: Python<'_>, + id_source: usize, + id_target: usize, + flux_source_target: f64, + flux_target_source: f64, +) -> RawFluxWrapper { + RawFluxWrapper(RawFlux { + id_source: id_source as u32, + id_target: id_target as u32, + flux_source_target, + flux_target_source, + }) +} + #[pyclass(name = "RawDataFlux")] pub struct RawDataFluxWrapper(cmtool_data::RawDataFlux); + #[pymethods] impl RawDataFluxWrapper { #[getter] pub fn n_zone(&self) -> usize { self.0.header.n_zone as usize } + + pub fn write(&self, path: &str) -> PyResult<()> { + if self.0.write_raw(path).is_ok() { + Ok(()) + } else { + Err(PyValueError::new_err("Vector not found")) + } + } } #[pyfunction] @@ -91,6 +123,32 @@ pub fn read_rawflow(path: &str) -> RawDataFluxWrapper { RawDataFluxWrapper(cmtool_data::RawDataFlux::read_raw(path).unwrap()) } +// impl FromPyObject for &[RawFluxWrapper] +// { +// type Error= +// fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result { + +// } +// } + +#[pyfunction] +pub fn vector_from_data<'py>( + _py: Python<'py>, + n_zone: usize, + x: Vec, +) -> PyResult { + let value: Vec = x.iter().map(|i| i.0).collect(); + let rd = RawDataFlux { + header: FluxFileHeader { + n_fluxes: value.len() as u32, + n_zone: n_zone as u32, + }, + fluxes: value, + }; + + Ok(RawDataFluxWrapper(rd)) +} + #[pyclass(name = "FlowMapDescriptor")] pub struct FlowMapDescriptorWrapper(cmtool_data::FlowMapDescriptor); #[pymethods] diff --git a/examples/python/complete_reactive.py b/examples/python/complete_reactive.py index cda69f32..f70b7056 100644 --- a/examples/python/complete_reactive.py +++ b/examples/python/complete_reactive.py @@ -24,12 +24,12 @@ """ import os +from typing import Callable import matplotlib.pyplot as plt import numpy as np import pycmtool from scipy.integrate import solve_ivp -from typing import Callable def integration( @@ -134,9 +134,12 @@ def check_mixing(fmt, n_s, final_time: float, reaction_rate): two_phase_flow = it.has_gas() n_p = 2 if two_phase_flow else 1 C = np.zeros((n_s, n_c, n_p)) + C[0, :, :] = 0.7 * initial_c_distribution(n_c, n_p) C[1, :, :] = 0.2 * initial_c_distribution(n_c, n_p) - C[2, :, 1] = 300e-3 + C[2, :, 0] = 9e-3 + if two_phase_flow: + C[2, :, 1] = 300e-3 m0 = gm0(two_phase_flow, C, it) sol = integration(two_phase_flow, fmt, m0, final_time, n_s, reaction_rate) @@ -179,9 +182,10 @@ def check_mixing(fmt, n_s, final_time: float, reaction_rate): if __name__ == "__main__": final_time = 15 * 3600 - root = os.environ["EXAMPLE_ROOT"] + root = os.environ["EXAMPLE_ROOT"] N_SPECIES = 3 + def _reaction_rate(Cl): mum = 0.8 / 3600 K_S = 0.1 diff --git a/examples/python/generate_reference.py b/examples/python/generate_reference.py new file mode 100644 index 00000000..2f94f1a8 --- /dev/null +++ b/examples/python/generate_reference.py @@ -0,0 +1,56 @@ +import numpy as np +import pycmtool + +if __name__ == "__main__": + NX, NY, NZ = 3, 3, 2 + n_zone = NX * NY * NZ + + vol_tot = 10.0 # L + + def zone_weight(x, y, z): + bx = x == 0 or x == NX - 1 + by = y == 0 or y == NY - 1 + bz = z == 0 or z == NZ - 1 + boundary_count = bx + by + bz + return {3: 0.5, 2: 0.8, 1: 1.2, 0: 2.0}[boundary_count] + + def zone_id(x, y, z): + return x + NX * y + NX * NY * z + + weights = np.array( + [zone_weight(x, y, z) for z in range(NZ) for y in range(NY) for x in range(NX)] + ) + # vol = vol_tot * weights / weights.sum() + vol = vol_tot / n_zone * np.ones((n_zone)) + vol_array = pycmtool.data.scalar_from_data(vol) + + Q_xy = 0.5 + Q_z = 0.3 + + flow_map = [] + + for z in range(NZ): + for y in range(NY): + for x in range(NX): + src = zone_id(x, y, z) + if x + 1 < NX: + tgt = zone_id(x + 1, y, z) + flow_map.append(pycmtool.data.new_raw_flux(src, tgt, Q_xy, Q_xy)) + if y + 1 < NY: + tgt = zone_id(x, y + 1, z) + flow_map.append(pycmtool.data.new_raw_flux(src, tgt, Q_xy, Q_xy)) + if z + 1 < NZ: + tgt = zone_id(x, y, z + 1) + flow_map.append(pycmtool.data.new_raw_flux(src, tgt, Q_z, Q_z)) + + fm = pycmtool.data.vector_from_data(n_zone, flow_map) + + fm.write("/tmp/fm/flowL.raw") + vol_array.write("/tmp/fm/vofL.raw") + + print(vol_array.data) + + print(fm.n_zone, vol_array.n_zone) + + v = pycmtool.data.read_flowmap("/tmp/fm/flowL.raw", "/tmp/fm/vofL.raw") + print(v.flowmap) diff --git a/examples/python/mixing_simple.py b/examples/python/mixing_simple.py index d8e5d0f8..f25c0367 100644 --- a/examples/python/mixing_simple.py +++ b/examples/python/mixing_simple.py @@ -130,12 +130,18 @@ def check_mixing(fmt, final_time: float): plt.xlabel("Compartment ID") plt.ylabel("Normalized concentration") plt.legend() + + plt.figure() + plt.style.use("tableau-colorblind10") + plt.plot(sol.t, y[0, 0, :]) + plt.legend() + plt.show() assert abs(m0m - mfm) < 1e-8 if __name__ == "__main__": - final_time = 20 + final_time = 200 root = os.environ["EXAMPLE_ROOT"] # Let CMTool read and load the full case automatically, ready to iterate diff --git a/examples/python/reactive_mixing.py b/examples/python/reactive_mixing.py index c634b2e4..d6b0fedd 100644 --- a/examples/python/reactive_mixing.py +++ b/examples/python/reactive_mixing.py @@ -67,10 +67,11 @@ def wrap(t: float, x: np.ndarray, f) -> np.ndarray: d_t = 0 # Not used for this transitioner # Advance iterator to the corresponding flowmap (according to t or dt) it = fmt.advance(t, d_t) + liq_state = it.liquid # Get the transition matrix - transition = pycmtool.get_sparse_transition_matrix(it) + transition = pycmtool.get_sparse_transition_matrix(liq_state) n_c = transition.shape[0] # Number of compartments - vol = it.volumes # Volume of each compartment + vol = liq_state.volumes # Volume of each compartment _mass = x.reshape((n_species, n_c)) # Reshape to (N_SPECIES, n_compartments) C = _mass / vol # Concentration: mass / volume R = np.zeros_like(C) @@ -79,8 +80,10 @@ def wrap(t: float, x: np.ndarray, f) -> np.ndarray: R[1, :] = r return (C @ transition + R).reshape(-1) # Return flattened array for ODE solver + w = lambda t, x: wrap(t, x, f) + return solve_ivp( - wrap, + w, (0, duration), mass_0.reshape(-1), method="BDF", @@ -98,26 +101,27 @@ def initial_c_distribution(n_c): # return m -def get_normalized(it, y, i): +def get_normalized(state, y, i): """ Calculate the normalized concentration for a given species across all compartments. The normalization is performed by dividing each compartment's concentration by the mean concentration of the species across all compartments. This is useful for comparing relative concentrations. """ - vol = it.volumes + vol = state.volumes m0_c = y[0, :, i] return (m0_c / vol) / np.mean(m0_c / vol, axis=0) def check_mixing(fmt, final_time: float): it = fmt.get_current() - transition = pycmtool.get_sparse_transition_matrix(it) + liq_state = it.liquid + transition = pycmtool.get_sparse_transition_matrix(liq_state) n_c = transition.shape[0] C = np.zeros((N_SPECIES, n_c)) C[0, :] = initial_c_distribution(n_c) - vol = it.volumes + vol = liq_state.volumes m0 = C * vol sol = integration(fmt, m0, final_time, N_SPECIES) @@ -125,20 +129,20 @@ def check_mixing(fmt, final_time: float): it = fmt.get_current() m0_c = y[0, :, 0] mt_c = y[0, :, -1] - c_init = get_normalized(fmt.get_at(0), y, 0) - c_final = get_normalized(fmt.get_at(fmt.n_flowmaps - 1), y, -1) + c_init = get_normalized(fmt.get_at(0).liquid, y, 0) + c_final = get_normalized(fmt.get_at(fmt.n_flowmaps - 1).liquid, y, -1) m0m = np.sum(m0_c, axis=0) mfm = np.sum(mt_c, axis=0) - print("Inital mass: ", m0m) - print("Final mass: ", mfm) - print("Initial normalized C: ", c_init[:5]) - print("Final normalized C: ", c_final[:5]) - print("Final variance: ", np.var(c_final, axis=0)) + # print("Inital mass: ", m0m) + # print("Final mass: ", mfm) + # print("Initial normalized C: ", c_init[:5]) + # print("Final normalized C: ", c_final[:5]) + # print("Final variance: ", np.var(c_final, axis=0)) plt.figure() - plt.plot(sol.t, y[0, 0, :]) - plt.plot(sol.t, y[1, 0, :]) + plt.plot(sol.t, y[0, 0, :], label="0") + plt.plot(sol.t, y[1, 0, :], label="1") plt.title("Concentration in compartment0") plt.legend() plt.show() From 4694a925e49cddd3968b77cf4917c34fc8b3bc3e Mon Sep 17 00:00:00 2001 From: bcasale Date: Wed, 25 Mar 2026 10:43:05 +0100 Subject: [PATCH 40/44] feat(core): clean code improve vtk support, add scale/shift operation for vector/scalar --- cmtool-core/src/ensight_gold/case.rs | 4 +- cmtool-core/src/errors.rs | 16 +- cmtool-core/src/lib.rs | 180 +++++++++++------ cmtool-core/src/model/interfaces.rs | 2 +- cmtool-core/src/model/mod.rs | 283 +++++++++++++++------------ cmtool-core/src/model/scalar.rs | 35 +++- cmtool-core/src/model/vectors.rs | 22 ++- cmtool-data/src/rawdata.rs | 9 + cmtool/src/main.rs | 43 +++- cmtool/src/sanitizer.rs | 12 +- 10 files changed, 391 insertions(+), 215 deletions(-) diff --git a/cmtool-core/src/ensight_gold/case.rs b/cmtool-core/src/ensight_gold/case.rs index c575c737..17d57afd 100644 --- a/cmtool-core/src/ensight_gold/case.rs +++ b/cmtool-core/src/ensight_gold/case.rs @@ -8,7 +8,7 @@ use std::{ use crate::CoreError; -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq, Eq)] pub enum VariableType { Scalar, Vector, @@ -26,7 +26,7 @@ impl TryInto for String { } } -#[derive(Default, Debug)] +#[derive(Default, Debug, Clone)] pub struct VariableInfo { pub var_type: String, pub name: String, diff --git a/cmtool-core/src/errors.rs b/cmtool-core/src/errors.rs index fa2c5507..f53fc741 100644 --- a/cmtool-core/src/errors.rs +++ b/cmtool-core/src/errors.rs @@ -2,11 +2,14 @@ use thiserror::Error; -// macro_rules! error_fmt { -// ($name:ident,$msg:literal) => { -// format!("CMTOOL({}):{}", name, msg) -// }; -// } +#[derive(Error, Debug)] +pub enum ModelError { + #[error("Cell to cell divergence higher than tolerance: {0}>{1}")] + CellToCellDivergence(f64, f64), + + #[error("Resulting flow has invalid value ")] + InvalidFlow, +} #[derive(Error, Debug)] pub enum CoreError { @@ -21,4 +24,7 @@ pub enum CoreError { #[error("Error writing/reading file: {0}")] IO(#[from] std::io::Error), + + #[error("Model: {0}")] + Model(#[from] ModelError), } diff --git a/cmtool-core/src/lib.rs b/cmtool-core/src/lib.rs index c8d46efe..a5fd638f 100644 --- a/cmtool-core/src/lib.rs +++ b/cmtool-core/src/lib.rs @@ -15,6 +15,7 @@ use grid::vtk::add_celldata_to_vtk; use cmtool_data::{RawData, RawDataFlux, RawDataScalar}; use model::{CMGeometry, CMModel, Scalar, Vector}; +use std::cmp::Ordering; use std::{path::Path, sync::Arc}; fn resolve_path( @@ -73,9 +74,6 @@ impl CMHandle { grid::MeshType::Cylindrical, )); - // let fullpath = format!("{}/wall_cart.scl1", root); - // let s = ensight_gold::scalar::ScalarField::init(eg_geometry, Path::new(&fullpath.clone()))?; - Ok(Self { model: Arc::new(CMModel::init(cm_geometry.clone())), _root_result: String::from("./test"), @@ -98,42 +96,29 @@ impl CMHandle { ) -> Result<(), CoreError> { std::fs::create_dir_all(&root_export)?; - // let mut handles = Vec::new(); + let mut rs = Vec::with_capacity(vars.len()); + let mut v: Vec = vars.to_owned(); + + v.sort_by(|a, b| { + if a.get_type() == ensight_gold::case::VariableType::Scalar { + Ordering::Less + } + // } else if b.get_type() == ensight_gold::case::VariableType::Scalar { + // Ordering::Less + // } + else { + Ordering::Equal + } + }); + for v in vars.iter() { - // let res_name = resolve_path(&root_export, &v.name) - // .as_ref() - // .to_str() - // .unwrap() - // .to_owned(); - - // let path = resolve_path(&root_input, &v.filepath) - // .as_ref() - // .to_str() - // .unwrap() - // .to_owned(); - // let eg_geometry_clone = self.eg_geometry.clone(); - // let cm_geometry_clone = self.cm_geometry.clone(); - // let model_clone = self.model.clone(); match v.get_type() { ensight_gold::case::VariableType::Scalar => { - self.dump_scalar( + let sc = self.dump_scalar( resolve_path(&root_export, &v.name), resolve_path(&root_input, &v.filepath), )?; - - // let eg_geometry_clone = self.eg_geometry.clone(); - // let cm_geometry_clone = self.cm_geometry.clone(); - // let model_clone = self.model.clone(); - - // handles.push(std::thread::spawn(move || { - // Self::ts_dump_scalar( - // res_name, - // path, - // eg_geometry_clone, - // cm_geometry_clone, - // model_clone, - // ) - // })); + rs.push((sc, v.name.clone())); } ensight_gold::case::VariableType::Vector => { self.dump_vector( @@ -144,41 +129,49 @@ impl CMHandle { } } - // for i in handles - // { - // match i.join() { - // Ok(Ok(_)) => println!("Thread executed successfully"), - // Ok(Err(e)) => println!("Thread failed with error: {:?}", e), - // Err(e) => println!("Thread panicked: {:?}", e), - // } - // } + #[cfg(feature = "use_vtk")] + self.export_vtk( + format!("{}/cma_case.vtu", root_export.as_ref().display()), + rs, + )?; Ok(()) } + pub fn get_scalar(&self, path: impl AsRef) -> Result { + Self::s_get_scalar(path, self.eg_geometry.clone(), self.cm_geometry.clone()) + } + + fn s_get_scalar( + path: impl AsRef, + eg_geometry: Arc, + cm_geometry: Arc, + ) -> Result { + let s = ensight_gold::scalar::ScalarField::init(eg_geometry.clone(), path)?; + Ok(Scalar::new(s, &cm_geometry, &eg_geometry)) + } + fn ts_dump_scalar( res_name: impl AsRef, path: impl AsRef, eg_geometry: Arc, cm_geometry: Arc, model: Arc, - ) -> Result<(), CoreError> { - let s = ensight_gold::scalar::ScalarField::init(eg_geometry.clone(), path)?; - - let scalar = Scalar::new(s, &cm_geometry, &eg_geometry); + ) -> Result { + let scalar = Self::s_get_scalar(path, eg_geometry, cm_geometry)?; let scalar_data = model.export_volume_integral_per_zone(scalar)?; scalar_data.write_raw(&format!("{}.raw", res_name.as_ref().to_str().unwrap()))?; - Ok(()) + Ok(scalar_data) } pub fn dump_scalar( &self, res_name: impl AsRef, path: impl AsRef, - ) -> Result<(), CoreError> { + ) -> Result { Self::ts_dump_scalar( res_name, path, @@ -196,6 +189,33 @@ impl CMHandle { Ok(()) } + pub fn dump_vector_phase_fraction( + &self, + res_name: impl AsRef, + path: impl AsRef, + phase_fraction: Scalar, + ) -> Result<(), CoreError> { + let v = ensight_gold::vectors::VectorField::init(self.eg_geometry.clone(), path)?; + let vector = + Vector::new(v, &self.cm_geometry, &self.eg_geometry).scale_by(phase_fraction)?; + + let flow_data = self.model.compute_flux_between_compartments(vector)?; + + flow_data.write_raw(&format!("{}.raw", res_name.as_ref().to_str().unwrap()))?; + Ok(()) + } + + pub fn dump_vector_raw( + &self, + res_name: impl AsRef, + vector: Vector, + ) -> Result<(), CoreError> { + let flow_data = self.model.compute_flux_between_compartments(vector)?; + + flow_data.write_raw(&format!("{}.raw", res_name.as_ref().to_str().unwrap()))?; + Ok(()) + } + pub fn dump_vector( &self, res_name: impl AsRef, @@ -209,17 +229,26 @@ impl CMHandle { Ok(()) } - pub fn dump_vector_from_scalar( + pub fn vector_from_scalar( &self, - res_name: impl AsRef, path_i: impl AsRef, path_j: impl AsRef, path_k: impl AsRef, - ) -> Result { + ) -> Result { let s = ensight_gold::scalar::ScalarField::init(self.eg_geometry.clone(), path_i)?; let sj = ensight_gold::scalar::ScalarField::init(self.eg_geometry.clone(), path_j)?; let sk = ensight_gold::scalar::ScalarField::init(self.eg_geometry.clone(), path_k)?; - let vector = Vector::from_scalar([s, sj, sk], &self.cm_geometry, &self.eg_geometry)?; + Vector::from_scalar([s, sj, sk], &self.cm_geometry, &self.eg_geometry) + } + + pub fn dump_vector_from_scalar( + &self, + res_name: impl AsRef, + path_i: impl AsRef, + path_j: impl AsRef, + path_k: impl AsRef, + ) -> Result { + let vector = self.vector_from_scalar(path_i, path_j, path_k)?; let flow_data = self.model.compute_flux_between_compartments(vector)?; flow_data.write_raw(&format!("{}.raw", res_name.as_ref().to_str().unwrap()))?; @@ -230,22 +259,53 @@ impl CMHandle { todo!() } + // #[cfg(feature = "use_vtk")] + // pub fn write_vtk(&self, path: impl AsRef) -> Result<(), CoreError> { + // let mesh = self.cm_geometry.get_grid().unwrap(); + // let p = path.as_ref().to_str().unwrap(); + // let mut vtk = mesh.get_vtk(p)?; + + // let volumes_data = self.model.get_real_volume(); + + // let volumes_data_array = vtkio::model::DataArray::scalars("real_volume", 1); + + // let volumes_data_array = volumes_data_array.with_vec(volumes_data); + + // add_celldata_to_vtk( + // &mut vtk, + // vtkio::model::Attribute::DataArray(volumes_data_array), + // ); + + // let mut vtk_bytes = Vec::::new(); + // vtk.write_xml(&mut vtk_bytes).unwrap(); + // std::fs::write(path, vtk_bytes).unwrap(); + + // Ok(()) + // } + #[cfg(feature = "use_vtk")] - pub fn write_vtk(&self, path: impl AsRef) -> Result<(), CoreError> { + fn export_vtk( + &self, + path: impl AsRef, + sc: Vec<(RawDataScalar, String)>, + ) -> Result<(), CoreError> { let mesh = self.cm_geometry.get_grid().unwrap(); let p = path.as_ref().to_str().unwrap(); let mut vtk = mesh.get_vtk(p)?; - let volumes_data = self.model.get_real_volume(); - - let volumes_data_array = vtkio::model::DataArray::scalars("real_volume", 1); + let mut add_cell = |name: &str, data: Vec| { + let data_array = vtkio::model::DataArray::scalars(name, 1); + let data_array = data_array.with_vec(data); + add_celldata_to_vtk(&mut vtk, vtkio::model::Attribute::DataArray(data_array)); + }; - let volumes_data_array = volumes_data_array.with_vec(volumes_data); + sc.iter().for_each(|(r, n)| { + let ve: Vec = r.values.iter().map(|i| i.value).collect(); + add_cell(n, ve) + }); - add_celldata_to_vtk( - &mut vtk, - vtkio::model::Attribute::DataArray(volumes_data_array), - ); + let volumes_data = self.model.get_real_volume(); + add_cell("real_volume", volumes_data); let mut vtk_bytes = Vec::::new(); vtk.write_xml(&mut vtk_bytes).unwrap(); diff --git a/cmtool-core/src/model/interfaces.rs b/cmtool-core/src/model/interfaces.rs index ed91a907..665cbbde 100644 --- a/cmtool-core/src/model/interfaces.rs +++ b/cmtool-core/src/model/interfaces.rs @@ -11,7 +11,7 @@ pub struct InterfaceInfo { pub target_id: usize, } -#[derive(Clone)] +#[derive(Clone, Copy, Debug)] pub struct InterfaceFlow { pub source_flow: f64, pub target_flow: f64, diff --git a/cmtool-core/src/model/mod.rs b/cmtool-core/src/model/mod.rs index dc0ad4f1..6b06a52c 100644 --- a/cmtool-core/src/model/mod.rs +++ b/cmtool-core/src/model/mod.rs @@ -5,16 +5,16 @@ use crate::{ CoreError, - coordinates::{CartesianCoordinates, CartesianVec3, CylindricalCoordinates}, - grid::MeshType, + coordinates::{CartesianCoordinates, CartesianVec3, Coords3}, + errors::ModelError, model::{ compartments::{CompartmentInfo, CountVolumeElement, ElementVolumeInfo}, interfaces::{AInterfacesInfo, InterfaceFlow, InterfaceInfo}, }, }; -use std::sync::Arc; +use std::{f64, sync::Arc}; mod data; -use cmtool_data::{RawDataFlux, RawDataScalar}; +use cmtool_data::{FluxFileHeader, RawDataFlux, RawDataScalar, RawFlux}; mod compartments; mod geometry; mod interfaces; @@ -31,6 +31,104 @@ pub struct CMModel { pub use geometry::CMGeometry; +fn get_data_flow( + n_zones: usize, + i_info: &[InterfaceInfo], + i_flow: &[InterfaceFlow], +) -> RawDataFlux { + let fluxes: Vec = i_info + .iter() + .zip(i_flow) + .map(|(k_i_nfo, k_i_flow)| RawFlux { + id_source: k_i_nfo.source_id as u32, + id_target: k_i_nfo.target_id as u32, + flux_source_target: k_i_flow.source_flow, + flux_target_source: k_i_flow.target_flow, + }) + .collect(); + RawDataFlux { + header: FluxFileHeader { + n_fluxes: i_flow.len() as u32, + n_zone: n_zones as u32, + }, + fluxes, + } + + // let mut flux_field = RawDataFlux::new(n_zones, i_flow.len()); + // for (i_interface, rd) in flux_field.fluxes.iter_mut().enumerate() { + // let InterfaceInfo { + // source_id, + // target_id, + // } = i_info[i_interface]; + // rd.id_source = source_id as u32; + // rd.id_target = target_id as u32; + // let InterfaceFlow { + // source_flow, + // target_flow, + // } = &i_flow[i_interface]; + // rd.flux_source_target = *source_flow; + // rd.flux_target_source = *target_flow; + // } + + // flux_field +} + +impl CMModel { + fn check_flux(n_zone: u32, rf: &RawFlux) -> bool { + let mut flag = false; + flag |= rf.flux_source_target.is_finite(); + flag |= rf.flux_source_target.is_sign_positive(); + flag |= rf.id_source < n_zone; + flag |= rf.id_target < n_zone; + flag + } + + pub fn check_flow(&self, raw: &RawDataFlux) -> Result<(), ModelError> { + const REL_TOLERANCE_DIVERGENCE_CELL: f64 = 1e-2; + const ABS_TOLERANCE_DIVERGENCE_CELL: f64 = 1e-7; + + let mut mass_balance: Vec = + vec![InterfaceFlow::default(); raw.header.n_zone as usize]; + let mut id_max = u32::MIN; + + for flow in raw.fluxes.iter() { + if !Self::check_flux(raw.header.n_zone, flow) { + return Err(ModelError::InvalidFlow); + } + + id_max = u32::max(id_max, flow.id_source); + //out + mass_balance[flow.id_source as usize].source_flow += flow.flux_target_source; + //in + mass_balance[flow.id_source as usize].target_flow += flow.flux_source_target; + + //in + mass_balance[flow.id_target as usize].source_flow += flow.flux_source_target; + //out + mass_balance[flow.id_target as usize].target_flow += flow.flux_target_source; + } + + for flow in &mass_balance { + let abs_diff = (flow.source_flow - flow.target_flow).abs(); + let denom = flow.source_flow.abs() + flow.target_flow.abs(); + + let err = if denom > f64::EPSILON { + 2.0 * abs_diff / denom + } else { + abs_diff + }; + if abs_diff > ABS_TOLERANCE_DIVERGENCE_CELL && err > REL_TOLERANCE_DIVERGENCE_CELL { + return Err(ModelError::CellToCellDivergence( + err, + REL_TOLERANCE_DIVERGENCE_CELL, + )); + } + } + + Ok(()) + } +} + impl CMModel { pub fn grid(&self) -> &dyn crate::grid::CompartmentMesh { self.geometry.get_grid().unwrap() @@ -54,8 +152,8 @@ impl CMModel { let (c_info, interfaces) = volume_element_count.into_reduce(); - if interfaces.n_interfaces() >= geometry.get_grid().as_ref().unwrap().n_maximum_interface() - { + // if interfaces.n_interfaces() >= geometry.get_grid().as_ref().unwrap().n_maximum_interface() + if interfaces.n_interfaces() > geometry.get_grid().as_ref().unwrap().n_maximum_interface() { unimplemented!( "RCMTOOL: CMModel::init: should have intefaces < n_maximum_interface {} {}", interfaces.n_interfaces(), @@ -81,91 +179,54 @@ impl CMModel { todo!() } + fn get_average_velocity( + &self, + vector: &Vector, + _curent_inteface_element: &[usize], + __curent_inteface_area: &[f64], + ) -> Coords3 { + let total_area: f64 = __curent_inteface_area.iter().sum(); + _curent_inteface_element + .iter() + .zip(__curent_inteface_area) + .fold([0.0f64; 3], |acc, (gid, a)| { + let v = CartesianVec3(vector.get_slice_xyz(*gid).to_owned()); + let CartesianCoordinates(centroid) = self.geometry.volume_elements.xyz[*gid]; + let theta = centroid[1].atan2(centroid[0]); + let cyl = v.to_cylindrical_vec(theta).0; + [ + acc[0] + cyl[0] * a / total_area, + acc[1] + cyl[1] * a / total_area, + acc[2] + cyl[2] * a / total_area, + ] + }) + } + pub fn compute_flux_between_compartments( &self, vector: Vector, ) -> Result { let n_fluxes = self.interfaces.n_interfaces(); - let mut flux_field = RawDataFlux::new(self.geometry.n_zone(), n_fluxes); - let mut flows: Vec = vec![Default::default(); n_fluxes]; - // for (i_interface, flow) in flows.iter_mut().enumerate() { - // let axis: usize = self.interfaces.normal_axis[i_interface]; - // let current_interface_area = &self.interfaces.area[i_interface]; - // let curent_inteface_element = &self.interfaces.global_id_from_interface[i_interface]; - - // for (global_id, area) in curent_inteface_element.iter().zip(current_interface_area) { - // let vector_value = CartesianVec3(vector.get_slice_xyz(*global_id).to_owned()); - - // let coords = match self.geometry.mesh_type { - // MeshType::Cylindrical => { - // let CartesianCoordinates(centroid) = - // self.geometry.volume_elements.xyz[*global_id]; - - // let CylindricalCoordinates(centroid) = - // CartesianCoordinates(centroid).into(); - - // vector_value.to_cylindrical_vec(centroid[1]).0 - - // // let cyl_vec = vector_value - // // .to_cylindrical_vec(self.interfaces.interface_theta[i_interface]) - // // .0; - // // cyl_vec - // // - // // - // // - // // let r = (centroid[0].powi(2) + centroid[1].powi(2)).sqrt(); - // // match axis { - // // 1 => [cyl_vec[0], cyl_vec[1] * r, cyl_vec[2]], - // // _ => cyl_vec, - // // } - // } - // _ => vector_value.0, - // }; - - // let f = coords[axis] * area; - - // if f > 0. { - // flow.source_flow += f - // } else if f < 0. { - // flow.target_flow += f.abs() - // } - // } - // } - for (i_interface, flow) in flows.iter_mut().enumerate() { let axis: usize = self.interfaces.normal_axis[i_interface]; - let current_interface_area = &self.interfaces.area[i_interface]; - let curent_inteface_element = &self.interfaces.global_id_from_interface[i_interface]; - let total_area: f64 = current_interface_area.iter().sum(); - let v_avg = curent_inteface_element - .iter() - .zip(current_interface_area) - .fold([0.0f64; 3], |acc, (gid, a)| { - let v = CartesianVec3(vector.get_slice_xyz(*gid).to_owned()); - let CartesianCoordinates(centroid) = self.geometry.volume_elements.xyz[*gid]; - let theta = centroid[1].atan2(centroid[0]); - let cyl = v.to_cylindrical_vec(theta).0; - [ - acc[0] + cyl[0] * a / total_area, - acc[1] + cyl[1] * a / total_area, - acc[2] + cyl[2] * a / total_area, - ] - }); - - let coords = v_avg; + let axis_oriented = crate::grid::index_to_oriented(axis); let source_id = self.interfaces.ids[i_interface].source_id; - let axis_oriented = crate::grid::index_to_oriented(axis); let theoretical_area = self .geometry .get_grid() .unwrap() .cell_surface(source_id, axis_oriented); - let f = coords[axis] * theoretical_area; + let current_interface_area = &self.interfaces.area[i_interface]; + let curent_inteface_element = &self.interfaces.global_id_from_interface[i_interface]; + let v_avg = + self.get_average_velocity(&vector, curent_inteface_element, current_interface_area); + + let f = v_avg[axis] * theoretical_area; if f > 0. { flow.source_flow += f @@ -176,39 +237,10 @@ impl CMModel { self.clean(&mut flows); - for (i_interface, rd) in flux_field.fluxes.iter_mut().enumerate() { - let InterfaceInfo { - source_id, - target_id, - } = self.interfaces.ids[i_interface]; - rd.id_source = source_id as u32; - rd.id_target = target_id as u32; - let InterfaceFlow { - source_flow, - target_flow, - } = &flows[i_interface]; - rd.flux_source_target = *source_flow; - rd.flux_target_source = *target_flow; - } - - // for (i_interface, rd) in flux_field.fluxes.iter_mut().enumerate() { - // let InterfaceInfo { - // source_id, - // target_id, - // } = self.interfaces.ids[i_interface]; - - // rd.id_source = source_id as u32; - // rd.id_target = target_id as u32; + let data_flow = get_data_flow(self.geometry.n_zone(), &self.interfaces.ids, &flows); - // let InterfaceFlow { - // source_flow, - // target_flow, - // } = &flows[i_interface]; - // rd.flux_source_target = *source_flow; - // rd.flux_target_source = *target_flow; - // } - - Ok(flux_field) + self.check_flow(&data_flow)?; + Ok(data_flow) } // pub fn export_volume_integral_per_zone(&self,scalar:&mut cmtool_data::RawDataScalar) { @@ -217,7 +249,9 @@ impl CMModel { fn clean(&self, flows: &mut [InterfaceFlow]) { const TOL: f64 = 1e-19; - const TOL_CONV: f64 = 1e-12; + const ABS_TOL_CONV: f64 = 1e-12; + const REL_TOL_CONV: f64 = 1e-6; + const MAX_IT: usize = 10000; let mut balance = vec![0.0f64; self.geometry.n_zone()]; let n_interfaces_per_zone = { @@ -248,13 +282,18 @@ impl CMModel { _balance[source_id] += flow.target_flow - flow.source_flow; _balance[target_id] += flow.source_flow - flow.target_flow; } - let total_balance = _balance.iter().map(|&x| x.abs()).sum::(); + // let total_balance = _balance.iter().map(|&x| x.abs()).sum::(); + let total_balance = _balance.iter().map(|&x| x * x).sum::().sqrt(); let total_flow = _flows .iter() .map(|flow| flow.target_flow + flow.source_flow) .sum::(); - total_balance / total_flow + if total_flow > f64::EPSILON { + total_balance / total_flow + } else { + total_balance + } }; let mut conv = false; @@ -264,11 +303,13 @@ impl CMModel { while it < MAX_IT && !conv { balance.fill(0.0); let max_err = f_err(&mut balance, flows); - if max_err < TOL { + + let delta = (err - max_err).abs(); + + conv = delta < ABS_TOL_CONV || delta / err.max(f64::EPSILON) < REL_TOL_CONV; + if conv { break; } - println!("{} {}", max_err, it); - conv = (err - max_err).abs() < TOL_CONV; err = max_err; it += 1; for (i_interface, flow) in flows.iter_mut().enumerate() { @@ -280,26 +321,8 @@ impl CMModel { flow.target_flow -= corr; } } - - // for i in 0..MAX_IT { - // balance.fill(0.0); - - // let max_err = f_err(&mut balance, flows); - - // println!("{} {}", max_err, i); - // if max_err < TOL { - // break; - // } - // for (i_interface, flow) in flows.iter_mut().enumerate() { - // let source_id = self.interfaces.ids[i_interface].source_id; - // let target_id = self.interfaces.ids[i_interface].target_id; - // let corr = (balance[source_id] - balance[target_id]) - // / (n_interfaces_per_zone[source_id] + n_interfaces_per_zone[target_id]) as f64; - // flow.source_flow += corr; - // flow.target_flow -= corr; - // } - // } } + pub fn export_volume_integral_per_zone( &self, model_scalar: Scalar, diff --git a/cmtool-core/src/model/scalar.rs b/cmtool-core/src/model/scalar.rs index 8814d407..4c033678 100644 --- a/cmtool-core/src/model/scalar.rs +++ b/cmtool-core/src/model/scalar.rs @@ -2,13 +2,16 @@ use std::ops::Index; +use cmtool_data::ScalarValueType; + use crate::{ + CoreError, ensight_gold::{self, types::ElementsType}, model::CMGeometry, }; pub struct Scalar { - value_in_vo: Vec, + pub(crate) value_in_vo: Vec, pub name: String, } @@ -44,6 +47,36 @@ impl Scalar { name: eg_scalar.get_name().to_string(), } } + + pub fn element_wise(self, a: &Self) -> Result { + if a.value_in_vo.len() != self.value_in_vo.len() { + return Err(CoreError::Custom(format!( + "Bad size for scalar scaling {} vs {} ", + self.value_in_vo.len(), + a.value_in_vo.len() + ))); + } + + let values: Vec = a + .value_in_vo + .iter() + .zip(&self.value_in_vo) + .map(|(a, b)| a * b) + .collect(); + Ok(Self { + value_in_vo: values, + name: format!("{} per {} scalar ", self.name, a.name), + }) + } + + pub fn scalar_shift(self, lambda: ScalarValueType) -> Self { + let value_in_vo = self.value_in_vo.iter().map(|i| lambda - i).collect(); + + Self { + value_in_vo, + name: format!("{} shifted by {} ", self.name, lambda), + } + } } impl Index for Scalar { diff --git a/cmtool-core/src/model/vectors.rs b/cmtool-core/src/model/vectors.rs index 41f4a2d2..4e84b5c4 100644 --- a/cmtool-core/src/model/vectors.rs +++ b/cmtool-core/src/model/vectors.rs @@ -3,7 +3,7 @@ use crate::{ CoreError, ensight_gold::{self, types::ElementsType}, - model::CMGeometry, + model::{self, CMGeometry, Scalar}, }; pub struct Vector { @@ -18,6 +18,26 @@ impl Vector { .expect("Slice with exactly 3 elements") } + pub fn scale_by(self, a: Scalar) -> Result { + // if self.value_in_vo.len() != a.value_in_vo.len() { + // return Err(CoreError::Custom(format!( + // "Bad size for vector scaling {} vs {} ", + // self.value_in_vo.len(), + // a.value_in_vo.len() + // ))); + // } + + let values: Vec = self + .value_in_vo + .chunks(3) + .zip(&a.value_in_vo) + .flat_map(|(triplet, &s)| triplet.iter().map(move |&v| v * s)) + .collect(); + Ok(Self { + value_in_vo: values, + }) + } + pub(crate) fn from_scalar( scalars: [ensight_gold::scalar::ScalarField; 3], geometry: &CMGeometry, diff --git a/cmtool-data/src/rawdata.rs b/cmtool-data/src/rawdata.rs index 612a89cb..97ee031f 100644 --- a/cmtool-data/src/rawdata.rs +++ b/cmtool-data/src/rawdata.rs @@ -199,6 +199,14 @@ impl From> for RawDataScalar { } } + + +// impl Into> for RawDataScalar { +// fn into(self) -> Vec { +// self.values.iter().map(|i| i.value).collect() +// } +// } + impl From<&[ScalarValueType]> for RawDataScalar { fn from(value: &[ScalarValueType]) -> Self { let len: u32 = value.len().try_into().unwrap_or_else(|_| { @@ -283,6 +291,7 @@ impl RawData for RawDataFlux { Ok(()) } } + pub trait FromBytes: Sized { fn from_bytes(buffer: &[u8], offset: &mut usize) -> Option; } diff --git a/cmtool/src/main.rs b/cmtool/src/main.rs index 5049acb0..605b90fe 100644 --- a/cmtool/src/main.rs +++ b/cmtool/src/main.rs @@ -19,7 +19,13 @@ fn main() -> Result<(), CmtoolError> { let mode = args.mode; match mode { AllModes::Cfd(cfdargs) => match cfdargs.mode { - Mode::Auto(autoargs) => auto_main(cfdargs.common, autoargs), + Mode::Auto(autoargs) => { + if let Err(e) = auto_main(cfdargs.common, autoargs) { + eprintln!("{}", e); + return Err(e); + } + return Ok(()); + } Mode::Manual(_manual_args) => todo!(), }, @@ -33,9 +39,9 @@ fn main() -> Result<(), CmtoolError> { fn auto_main(common: CommonArgs, autoargs: AutoArgs) -> Result<(), CmtoolError> { let stem = Path::new(&autoargs.case_path) - .file_stem() // Gets "casename" as OsStr + .file_stem() .and_then(|s| s.to_str()) - .unwrap(); // Converts OsStr to &str + .unwrap(); let root_dir = out_or_default(common.out); @@ -58,19 +64,38 @@ fn auto_main(common: CommonArgs, autoargs: AutoArgs) -> Result<(), CmtoolError> handle.dump_real_volume(format!("{}/{}/vofL", root_dir, stem))?; handle.dump_real_volume(format!("{}/{}/vtot", root_dir, stem))?; + // let path_gas_f = case.paths.iter().find(|f| f.name == "gas_vof").unwrap(); + + // let liquid_fraction = handle + // .get_scalar(std::path::PathBuf::from(&case.root).join(&path_gas_f.filepath)) + // .unwrap() + // .scalar_shift(1.); + + // let manual_flowl = handle + // .vector_from_scalar( + // "/tmp/inputs/RESULTS.scl1", + // "/tmp/inputs/RESULTS.scl2", + // "/tmp/inputs/RESULTS.scl3", + // ) + // .unwrap() + // .scale_by(liquid_fraction) + // .unwrap(); + + // handle.dump_vector_raw(format!("{}/{}/flowL", root_dir, stem), manual_flowl)?; + // handle.dump_vector_from_scalar( // format!("{}/{}/flowL", root_dir, stem), - // "/tmp/sanofi/inputs/RESULTS.scl1", - // "/tmp/sanofi/inputs/RESULTS.scl2", - // "/tmp/sanofi/inputs/RESULTS.scl3", + // "/tmp/sanofi/inputs/RESULTS.scl1", + // "/tmp/sanofi/inputs/RESULTS.scl2", + // "/tmp/sanofi/inputs/RESULTS.scl3", // )?; - #[cfg(feature = "use_vtk")] - handle.write_vtk(format!("{}/{}/cma_case.vtu", root_dir, stem)); + // #[cfg(feature = "use_vtk")] + // handle.write_vtk(format!("{}/{}/cma_case.vtu", root_dir, stem)); let f = cmtool::check_flows( handle.grid(), - &RawDataFlux::read_raw("./out/cuve_sldmsh_initmrf/velocity.raw").unwrap(), + &RawDataFlux::read_raw("./out/RESULTS/flowL.raw").unwrap(), ) .unwrap(); // println!("{}", f); diff --git a/cmtool/src/sanitizer.rs b/cmtool/src/sanitizer.rs index 5a889de9..d2f51e44 100644 --- a/cmtool/src/sanitizer.rs +++ b/cmtool/src/sanitizer.rs @@ -21,14 +21,14 @@ pub fn check_flows( // let boundary = grid.get_boundary(); for flow in raw_flows.fluxes.iter() { - // mass_balance[flow.id_target as usize].0 += flow.flux_source_target; - // mass_balance[flow.id_source as usize].1 += flow.flux_source_target; - // - // - mass_balance[flow.id_target as usize].0 += flow.flux_source_target; + //out + mass_balance[flow.id_source as usize].0 += flow.flux_target_source; + //in mass_balance[flow.id_source as usize].1 += flow.flux_source_target; - mass_balance[flow.id_source as usize].0 += flow.flux_target_source; + //in + mass_balance[flow.id_target as usize].0 += flow.flux_source_target; + //out mass_balance[flow.id_target as usize].1 += flow.flux_target_source; } From f1829fae2b5751446a604d0d6c1d6281b8023afe Mon Sep 17 00:00:00 2001 From: bcasale Date: Wed, 25 Mar 2026 11:21:51 +0100 Subject: [PATCH 41/44] fix(data): remove rounding error on flowmap/probability/neighbor calculation --- cmtool-data/src/flowmap.rs | 33 +++++++++++++------- cmtool-data/src/rawdata.rs | 33 ++++++++++++++------ cmtool-data/src/states.rs | 64 +++++++++++++++++++++++++++----------- 3 files changed, 91 insertions(+), 39 deletions(-) diff --git a/cmtool-data/src/flowmap.rs b/cmtool-data/src/flowmap.rs index 2a2226a4..bd64611d 100644 --- a/cmtool-data/src/flowmap.rs +++ b/cmtool-data/src/flowmap.rs @@ -26,31 +26,36 @@ impl FlowMapDescriptor { let n_zone = data_flows.header.n_zone as usize; let mut flowmap = Array2::::zeros((n_zone, n_zone)); - let mut neighbors: Vec> = vec![Vec::new(); n_zone]; //Vec::with_capacity(data.header.n_zone as usize) + let mut neighbors: Vec> = vec![Vec::with_capacity(10); n_zone]; if data_flows.header.n_zone != data_volumes.header.n_zone { return Err(DataError::BadData); } - for RawFlux { + let mut add_at = |i: usize, j: usize, val: f64| -> Result<(), DataError> { + //Error should never be triggered, by construction i= 0.); + assert!(flux_target_source >= 0.); - if let Some(g) = flowmap.get_mut((id_source, id_target)) { - *g += flux_source_target; - } + add_at(id_source, id_target, flux_source_target)?; + add_at(id_target, id_source, flux_target_source)?; - if let Some(g) = flowmap.get_mut((id_target, id_source)) { - *g += flux_target_source; - } neighbors[id_source].push(id_target); - neighbors[id_target].push(id_source) + neighbors[id_target].push(id_source); } let max_size = neighbors @@ -65,9 +70,13 @@ impl FlowMapDescriptor { for (i_zone, neighbors_for_zone) in neighbors.iter().enumerate() { for (i_n, id_neighbor) in neighbors_for_zone.iter().enumerate() { - *(neighbor_flat.get_mut((i_zone, i_n)).unwrap()) = *id_neighbor; + // *(neighbor_flat.get_mut((i_zone, i_n)).unwrap()) = *id_neighbor; + *(neighbor_flat + .get_mut((i_zone, i_n)) + .expect("Flat neighbor out of bound")) = *id_neighbor; } } + let volumes: Vec = data_volumes.values.iter().map(|v| v.value).collect(); Ok(FlowMapDescriptor { diff --git a/cmtool-data/src/rawdata.rs b/cmtool-data/src/rawdata.rs index 97ee031f..27791db9 100644 --- a/cmtool-data/src/rawdata.rs +++ b/cmtool-data/src/rawdata.rs @@ -199,8 +199,6 @@ impl From> for RawDataScalar { } } - - // impl Into> for RawDataScalar { // fn into(self) -> Vec { // self.values.iter().map(|i| i.value).collect() @@ -276,6 +274,10 @@ impl RawData for RawDataFlux { fluxes.push(RawFlux::from_bytes(&buffer, &mut offset)?); } + if fluxes.len() as u32 != header.n_fluxes { + return None; + } + Some(RawDataFlux { header, fluxes }) } @@ -402,6 +404,11 @@ impl FromBytes for RawFlux { .unwrap(), ); *offset += size_of::(); + + if flux_source_target < 0. || flux_target_source < 0. { + return None; + } + Some(RawFlux { id_source, id_target, @@ -585,14 +592,22 @@ mod tests { let raw_data_flux = RawDataFlux { header: FluxFileHeader { n_zone: 10, - n_fluxes: 100, + n_fluxes: 2, }, - fluxes: vec![RawFlux { - id_source: 1, - id_target: 2, - flux_source_target: 0.1, - flux_target_source: 2.71, - }], + fluxes: vec![ + RawFlux { + id_source: 1, + id_target: 2, + flux_source_target: 0.1, + flux_target_source: 2.71, + }, + RawFlux { + id_source: 1, + id_target: 2, + flux_source_target: 0.1, + flux_target_source: 2.71, + }, + ], }; let path = "./tes2t.raw"; diff --git a/cmtool-data/src/states.rs b/cmtool-data/src/states.rs index 12e5b98c..c3ba1695 100644 --- a/cmtool-data/src/states.rs +++ b/cmtool-data/src/states.rs @@ -4,16 +4,35 @@ use crate::FlowMapDescriptor; use nalgebra_sparse::CooMatrix; use ndarray::Array2; -macro_rules! non_zero { - ($i:ident, $eps:expr) => { - ($i.abs() > $eps) +macro_rules! almost_equal { + ($i:expr, $base:expr, $eps:expr) => { + (($i - $base).abs() < $eps) }; } +// macro_rules! non_zero { +// ($i:expr, $eps:expr) => { +// ($i.abs() > $eps) +// }; +// } + +// macro_rules! near_one { +// ($i:expr, $eps:expr) => { +// almost_equal!($i, 1.0, $eps) +// }; +// } + +macro_rules! round_if_needed { + ($val:expr, $base:expr, $tol:expr) => {{ + if almost_equal!($val, $base, $tol) { + $base + } else { + $val + } + }}; +} fn get_transition_from_fm(fm: Array2) -> (CooMatrix, Vec) { let n_compartments: usize = fm.nrows(); - const EPS: f64 = 1e-7; - // let row_sum = fm.sum_axis(Axis(1)); let mut transition = CooMatrix::new(n_compartments, n_compartments); let mut row_sum = vec![0.; n_compartments]; @@ -22,9 +41,7 @@ fn get_transition_from_fm(fm: Array2) -> (CooMatrix, Vec) { for i_col in 0..n_compartments { if i_row != i_col { let val = *fm.get((i_row, i_col)).expect("Bad formated flowmap"); - if non_zero!(val, EPS) { - transition.push(i_row, i_col, val); - } + transition.push(i_row, i_col, val); row_sum[i_row] += val; } } @@ -32,10 +49,9 @@ fn get_transition_from_fm(fm: Array2) -> (CooMatrix, Vec) { (0..n_compartments).for_each(|i_row| { let val = row_sum[i_row]; - if non_zero!(val, EPS) { - transition.push(i_row, i_row, -val); - } + transition.push(i_row, i_row, -val); }); + (transition, row_sum) } @@ -44,33 +60,45 @@ fn get_probability(liquid_neighors: &Array2, transition: &CooMatrix) use nalgebra_sparse::CscMatrix; let shape = liquid_neighors.dim(); - let mut proba = Array2::::zeros(shape); + //Start with filled with one array to ensure that probability will be increasing + let mut proba = Array2::::ones(shape); let transition_csc = CscMatrix::from(transition); - + let ghost_neighor = shape.0 + 1; (0..shape.0).for_each(|i_compartment| { let mut cumsum = 0.; let mut count_neighbor = 0; - liquid_neighors.row(i_compartment).for_each(|i_neighbor| { - if *i_neighbor != i_compartment { + liquid_neighors.row(i_compartment).for_each(|&i_neighbor| { + if i_neighbor != ghost_neighor { let out_flow = transition_csc .index_entry(i_compartment, i_compartment) .into_value(); let proba_out: f64 = if out_flow != 0. { transition_csc - .index_entry(i_compartment, *i_neighbor) + .index_entry(i_compartment, i_neighbor) .into_value() / out_flow.abs() } else { 0. }; + debug_assert!(proba_out >= 0.); + //round to one if close enough + let p_cp = round_if_needed!(proba_out + cumsum, 1., 1e-8); + + *proba + .get_mut((i_compartment, count_neighbor)) + .expect("Probability out of bound") = p_cp; - let p_cp = proba_out + cumsum; - *proba.get_mut((i_compartment, count_neighbor)).unwrap() = p_cp; cumsum += proba_out; } count_neighbor += 1; }); + assert!( + (cumsum - 1.0).abs() < 1e-10, + "compartment {} cumulative probability = {} < 1 (not conservative)", + i_compartment, + cumsum + ); }); proba From fa22fad2a1395db1669b23e71f1da989baeacdb9 Mon Sep 17 00:00:00 2001 From: bcasale Date: Wed, 25 Mar 2026 12:33:41 +0100 Subject: [PATCH 42/44] fix(states): probability leaving do not trigger assert for 0d flowmap --- cmtool-data/src/states.rs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/cmtool-data/src/states.rs b/cmtool-data/src/states.rs index c3ba1695..1da787b8 100644 --- a/cmtool-data/src/states.rs +++ b/cmtool-data/src/states.rs @@ -59,6 +59,11 @@ fn get_transition_from_fm(fm: Array2) -> (CooMatrix, Vec) { fn get_probability(liquid_neighors: &Array2, transition: &CooMatrix) -> Array2 { use nalgebra_sparse::CscMatrix; + //TODO: this method is called even though there's no flow (0D) + //To allow correct behaviour, assert has the condition outflow==0 + //Find a way cleaner way to : skip test and do not trigger assert + + //TODO change assert to real real and return Result<> let shape = liquid_neighors.dim(); //Start with filled with one array to ensure that probability will be increasing let mut proba = Array2::::ones(shape); @@ -66,13 +71,17 @@ fn get_probability(liquid_neighors: &Array2, transition: &CooMatrix) let ghost_neighor = shape.0 + 1; (0..shape.0).for_each(|i_compartment| { let mut cumsum = 0.; + let out_flow = round_if_needed!( + transition_csc + .index_entry(i_compartment, i_compartment) + .into_value(), + 0., + 1e-12 + ); + let mut count_neighbor = 0; liquid_neighors.row(i_compartment).for_each(|&i_neighbor| { if i_neighbor != ghost_neighor { - let out_flow = transition_csc - .index_entry(i_compartment, i_compartment) - .into_value(); - let proba_out: f64 = if out_flow != 0. { transition_csc .index_entry(i_compartment, i_neighbor) @@ -93,8 +102,9 @@ fn get_probability(liquid_neighors: &Array2, transition: &CooMatrix) } count_neighbor += 1; }); + assert!( - (cumsum - 1.0).abs() < 1e-10, + (cumsum - 1.0).abs() < 1e-10 || out_flow == 0., "compartment {} cumulative probability = {} < 1 (not conservative)", i_compartment, cumsum @@ -162,6 +172,10 @@ impl IterationState { let liquid_neighors = liq.neighbors.clone(); //Todo find way to remove clone let liq_state: HydroState = liq.into(); + + //TODO + // call this only if liq has flow (transition and neighbors), 0D maps do not have + //proba #[cfg(feature = "probability")] let liquid_cumulative_probability = get_probability(&liquid_neighors, &liq_state.transition); From f918b1a070ce36345f2d63547bcef8c8a754a567 Mon Sep 17 00:00:00 2001 From: bcasale Date: Tue, 28 Apr 2026 15:36:54 +0200 Subject: [PATCH 43/44] fix(CDF): hot fix, remove assertion in cumulative proba which are not compatible with PFR run --- cmtool-data/src/states.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/cmtool-data/src/states.rs b/cmtool-data/src/states.rs index 1da787b8..6ec60c2b 100644 --- a/cmtool-data/src/states.rs +++ b/cmtool-data/src/states.rs @@ -74,11 +74,14 @@ fn get_probability(liquid_neighors: &Array2, transition: &CooMatrix) let out_flow = round_if_needed!( transition_csc .index_entry(i_compartment, i_compartment) - .into_value(), + .into_value() + .abs(), 0., 1e-12 ); + //TODO PFR lead to cumsum==-1 how to handle assertion ? + let mut count_neighbor = 0; liquid_neighors.row(i_compartment).for_each(|&i_neighbor| { if i_neighbor != ghost_neighor { @@ -86,7 +89,7 @@ fn get_probability(liquid_neighors: &Array2, transition: &CooMatrix) transition_csc .index_entry(i_compartment, i_neighbor) .into_value() - / out_flow.abs() + / out_flow } else { 0. }; @@ -102,13 +105,16 @@ fn get_probability(liquid_neighors: &Array2, transition: &CooMatrix) } count_neighbor += 1; }); - - assert!( - (cumsum - 1.0).abs() < 1e-10 || out_flow == 0., - "compartment {} cumulative probability = {} < 1 (not conservative)", - i_compartment, - cumsum - ); + //TODO PFR lead to cumsum==-1 how to handle assertion ? + // Idea: + // let is_pfr = i_compartment == 0 || i_compartment == liquid_neighors.len() - 1; + //For PFR NEED TO REMOVE THISASSERT FIXME + // assert!( + // (cumsum - 1.0).abs() < 1e-10 || out_flow == 0., + // "compartment {} cumulative probability = {} < 1 (not conservative)", + // i_compartment, + // cumsum + // ); }); proba From 859c43c530e9c6b1fd68d09927fee4b157126e68 Mon Sep 17 00:00:00 2001 From: bcasale Date: Tue, 28 Apr 2026 15:41:51 +0200 Subject: [PATCH 44/44] chore: clippy prepare merge --- cmtool-core/src/lib.rs | 2 +- cmtool-core/src/model/interfaces.rs | 4 ++-- cmtool-core/src/model/mod.rs | 2 +- cmtool-core/src/model/vectors.rs | 2 +- cmtool-core/src/utils/area.rs | 36 ++++++++++++++--------------- cmtool/src/main.rs | 4 ++-- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/cmtool-core/src/lib.rs b/cmtool-core/src/lib.rs index a5fd638f..a9049f80 100644 --- a/cmtool-core/src/lib.rs +++ b/cmtool-core/src/lib.rs @@ -99,7 +99,7 @@ impl CMHandle { let mut rs = Vec::with_capacity(vars.len()); let mut v: Vec = vars.to_owned(); - v.sort_by(|a, b| { + v.sort_by(|a, _b| { if a.get_type() == ensight_gold::case::VariableType::Scalar { Ordering::Less } diff --git a/cmtool-core/src/model/interfaces.rs b/cmtool-core/src/model/interfaces.rs index 665cbbde..949d26b2 100644 --- a/cmtool-core/src/model/interfaces.rs +++ b/cmtool-core/src/model/interfaces.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later use crate::coordinates::*; -use crate::grid::{NeighborDirection, index_to_oriented}; +use crate::grid::NeighborDirection; use crate::model::CMGeometry; use crate::utils::compute_intersection_area; @@ -128,7 +128,7 @@ impl AInterfacesInfo { // self.check_areas(geometry); } - + #[allow(unused)] fn check_areas(&self, geometry: &CMGeometry) { let grid = geometry.get_grid().unwrap(); let n_zones = geometry.n_zone(); diff --git a/cmtool-core/src/model/mod.rs b/cmtool-core/src/model/mod.rs index 6b06a52c..b35b7e36 100644 --- a/cmtool-core/src/model/mod.rs +++ b/cmtool-core/src/model/mod.rs @@ -248,7 +248,7 @@ impl CMModel { // } fn clean(&self, flows: &mut [InterfaceFlow]) { - const TOL: f64 = 1e-19; + // const TOL: f64 = 1e-19; const ABS_TOL_CONV: f64 = 1e-12; const REL_TOL_CONV: f64 = 1e-6; diff --git a/cmtool-core/src/model/vectors.rs b/cmtool-core/src/model/vectors.rs index 4e84b5c4..827b5e08 100644 --- a/cmtool-core/src/model/vectors.rs +++ b/cmtool-core/src/model/vectors.rs @@ -3,7 +3,7 @@ use crate::{ CoreError, ensight_gold::{self, types::ElementsType}, - model::{self, CMGeometry, Scalar}, + model::{CMGeometry, Scalar}, }; pub struct Vector { diff --git a/cmtool-core/src/utils/area.rs b/cmtool-core/src/utils/area.rs index 32841388..799aa4c4 100644 --- a/cmtool-core/src/utils/area.rs +++ b/cmtool-core/src/utils/area.rs @@ -5,24 +5,24 @@ use crate::{ ensight_gold::types::{ElementsType, VolumeElementTypes}, }; -fn sort_polygon_ccw(points: &[[f64; 2]]) -> Vec<[f64; 2]> { - let centroid = { - let (mut sx, mut sy) = (0.0, 0.0); - for p in points { - sx += p[0]; - sy += p[1]; - } - [sx / points.len() as f64, sy / points.len() as f64] - }; - - let mut sorted = points.to_vec(); - sorted.sort_by(|a, b| { - let angle_a = (a[1] - centroid[1]).atan2(a[0] - centroid[0]); - let angle_b = (b[1] - centroid[1]).atan2(b[0] - centroid[0]); - angle_a.partial_cmp(&angle_b).unwrap() - }); - sorted -} +// fn sort_polygon_ccw(points: &[[f64; 2]]) -> Vec<[f64; 2]> { +// let centroid = { +// let (mut sx, mut sy) = (0.0, 0.0); +// for p in points { +// sx += p[0]; +// sy += p[1]; +// } +// [sx / points.len() as f64, sy / points.len() as f64] +// }; + +// let mut sorted = points.to_vec(); +// sorted.sort_by(|a, b| { +// let angle_a = (a[1] - centroid[1]).atan2(a[0] - centroid[0]); +// let angle_b = (b[1] - centroid[1]).atan2(b[0] - centroid[0]); +// angle_a.partial_cmp(&angle_b).unwrap() +// }); +// sorted +// } fn project_points_to_plane_2d(points: &[[f64; 3]], normal: &CartesianVec3) -> Vec<[f64; 2]> { let n = normal.normalized(); diff --git a/cmtool/src/main.rs b/cmtool/src/main.rs index 605b90fe..1ca5c3df 100644 --- a/cmtool/src/main.rs +++ b/cmtool/src/main.rs @@ -24,7 +24,7 @@ fn main() -> Result<(), CmtoolError> { eprintln!("{}", e); return Err(e); } - return Ok(()); + Ok(()) } Mode::Manual(_manual_args) => todo!(), @@ -93,7 +93,7 @@ fn auto_main(common: CommonArgs, autoargs: AutoArgs) -> Result<(), CmtoolError> // #[cfg(feature = "use_vtk")] // handle.write_vtk(format!("{}/{}/cma_case.vtu", root_dir, stem)); - let f = cmtool::check_flows( + let _f = cmtool::check_flows( handle.grid(), &RawDataFlux::read_raw("./out/RESULTS/flowL.raw").unwrap(), )