diff --git a/environments/py-3.10-linux-64-dev.conda.lock.yml b/environments/py-3.10-linux-64-dev.conda.lock.yml index 39f10a75..d180b782 100644 --- a/environments/py-3.10-linux-64-dev.conda.lock.yml +++ b/environments/py-3.10-linux-64-dev.conda.lock.yml @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: d20cb4f62773f79495ca5432e662a34453afa41e6084f06c349fbb84600a44bb +# input_hash: 0f54a26695792bef9ae71d11e72896171987869d09020c611e73c9a0622596d6 channels: - conda-forge @@ -139,6 +139,7 @@ dependencies: - libpng=1.6.49=h943b412_0 - libscotch=7.0.6=hea33c07_1 - libsodium=1.0.20=h4ab18f5_0 + - libspatialindex=2.0.0=he02047a_0 - libsqlite=3.50.1=h6cd9bfd_7 - libssh2=1.11.1=hcf80075_0 - libstdcxx=15.1.0=h8f9b012_3 @@ -202,7 +203,7 @@ dependencies: - pthread-stubs=0.4=hb9d3cd8_1002 - ptyprocess=0.7.0=pyhd8ed1ab_1 - pure_eval=0.2.3=pyhd8ed1ab_1 - - pybtex=0.25.0=pyhd8ed1ab_1 + - pybtex=0.25.1=pyhd8ed1ab_0 - pybtex-docutils=1.0.3=py310hff52083_2 - pycparser=2.22=pyh29332c3_1 - pydantic=2.11.7=pyh3cfb1c2_0 @@ -233,6 +234,7 @@ dependencies: - rfc3339-validator=0.1.4=pyhd8ed1ab_1 - rfc3986-validator=0.1.1=pyh9f0ad1d_0 - rpds-py=0.25.1=py310hbcd0ec0_0 + - rtree=1.2.0=py310haf1e407_1 - scikit-learn=1.4.2=py310h981052a_1 - scipy=1.14.1=py310hfcf56fc_2 - send2trash=1.8.3=pyh0d859eb_1 @@ -275,6 +277,7 @@ dependencies: - tornado=6.5.1=py310ha75aee5_0 - tqdm=4.67.1=pyhd8ed1ab_1 - traitlets=5.14.3=pyhd8ed1ab_1 + - trimesh=4.1.8=pyhd8ed1ab_0 - types-python-dateutil=2.9.0.20250516=pyhd8ed1ab_0 - typing-extensions=4.14.0=h32cad80_0 - typing-inspection=0.4.1=pyhd8ed1ab_0 @@ -302,7 +305,7 @@ dependencies: - zstandard=0.23.0=py310ha75aee5_2 - zstd=1.5.7=hb8e6e7a_2 - pip: - - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@3936709de7ce3cd7d469b6c88f34bb0a704670de + - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@ea323dc1e48634a19cca2b2693f989fae08880f1 - geoh5py @ git+https://github.com/MiraGeoscience/geoh5py.git@99695a5e34812bfbb53cef84803033a91af137de - mira-simpeg @ git+https://github.com/MiraGeoscience/simpeg.git@f72a1367edcb2da969002ca06f18f532340b3c27 - octree-creation-app @ git+https://github.com/MiraGeoscience/octree-creation-app.git@02fbd85bf7d54b8f4336f1f0094c1c3e27714e81 diff --git a/environments/py-3.10-linux-64.conda.lock.yml b/environments/py-3.10-linux-64.conda.lock.yml index 6bf97c87..f9bd78c1 100644 --- a/environments/py-3.10-linux-64.conda.lock.yml +++ b/environments/py-3.10-linux-64.conda.lock.yml @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: d20cb4f62773f79495ca5432e662a34453afa41e6084f06c349fbb84600a44bb +# input_hash: 0f54a26695792bef9ae71d11e72896171987869d09020c611e73c9a0622596d6 channels: - conda-forge @@ -77,6 +77,7 @@ dependencies: - libnsl=2.0.1=hb9d3cd8_1 - libpng=1.6.49=h943b412_0 - libscotch=7.0.6=hea33c07_1 + - libspatialindex=2.0.0=he02047a_0 - libsqlite=3.50.1=h6cd9bfd_7 - libssh2=1.11.1=hcf80075_0 - libstdcxx=15.1.0=h8f9b012_3 @@ -125,6 +126,7 @@ dependencies: - pytz=2025.2=pyhd8ed1ab_0 - pyyaml=6.0.2=py310h89163eb_2 - readline=8.2=h8c095d6_2 + - rtree=1.2.0=py310haf1e407_1 - scikit-learn=1.4.2=py310h981052a_1 - scipy=1.14.1=py310hfcf56fc_2 - setuptools=80.9.0=pyhff2d567_0 @@ -137,6 +139,7 @@ dependencies: - toolz=1.0.0=pyhd8ed1ab_1 - tornado=6.5.1=py310ha75aee5_0 - tqdm=4.67.1=pyhd8ed1ab_1 + - trimesh=4.1.8=pyhd8ed1ab_0 - typing-extensions=4.14.0=h32cad80_0 - typing-inspection=0.4.1=pyhd8ed1ab_0 - typing_extensions=4.14.0=pyhe01879c_0 @@ -154,7 +157,7 @@ dependencies: - zstandard=0.23.0=py310ha75aee5_2 - zstd=1.5.7=hb8e6e7a_2 - pip: - - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@3936709de7ce3cd7d469b6c88f34bb0a704670de + - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@ea323dc1e48634a19cca2b2693f989fae08880f1 - geoh5py @ git+https://github.com/MiraGeoscience/geoh5py.git@99695a5e34812bfbb53cef84803033a91af137de - mira-simpeg @ git+https://github.com/MiraGeoscience/simpeg.git@f72a1367edcb2da969002ca06f18f532340b3c27 - octree-creation-app @ git+https://github.com/MiraGeoscience/octree-creation-app.git@02fbd85bf7d54b8f4336f1f0094c1c3e27714e81 diff --git a/environments/py-3.10-win-64-dev.conda.lock.yml b/environments/py-3.10-win-64-dev.conda.lock.yml index 9e3e8d2e..1f85b346 100644 --- a/environments/py-3.10-win-64-dev.conda.lock.yml +++ b/environments/py-3.10-win-64-dev.conda.lock.yml @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: win-64 -# input_hash: eaae335cf36adc80372dc46b8513b3371068cb4b78164f9bf33218ae884e5a43 +# input_hash: fe9bea64dc53fd535084a0a107d7688e8632e51eeb8084b72468b0a651a364b6 channels: - conda-forge @@ -130,6 +130,7 @@ dependencies: - liblzma=5.8.1=h2466b09_2 - libpng=1.6.49=h7a4582a_0 - libsodium=1.0.20=hc70643c_0 + - libspatialindex=2.0.0=h5a68840_0 - libsqlite=3.50.1=hf5d6505_7 - libssh2=1.11.1=h9aa295b_0 - libtiff=4.7.0=h05922d8_5 @@ -185,7 +186,7 @@ dependencies: - psutil=7.0.0=py310ha8f682b_0 - pthread-stubs=0.4=h0e40799_1002 - pure_eval=0.2.3=pyhd8ed1ab_1 - - pybtex=0.25.0=pyhd8ed1ab_1 + - pybtex=0.25.1=pyhd8ed1ab_0 - pybtex-docutils=1.0.3=py310h5588dad_2 - pycparser=2.22=pyh29332c3_1 - pydantic=2.11.7=pyh3cfb1c2_0 @@ -217,6 +218,7 @@ dependencies: - rfc3339-validator=0.1.4=pyhd8ed1ab_1 - rfc3986-validator=0.1.1=pyh9f0ad1d_0 - rpds-py=0.25.1=py310hed05c55_0 + - rtree=1.2.0=py310h08d5ad2_1 - scikit-learn=1.4.2=py310hf2a6c47_1 - scipy=1.14.1=py310hbd0dde3_2 - send2trash=1.8.3=pyh5737063_1 @@ -259,6 +261,7 @@ dependencies: - tornado=6.5.1=py310ha8f682b_0 - tqdm=4.67.1=pyhd8ed1ab_1 - traitlets=5.14.3=pyhd8ed1ab_1 + - trimesh=4.1.8=pyhd8ed1ab_0 - types-python-dateutil=2.9.0.20250516=pyhd8ed1ab_0 - typing-extensions=4.14.0=h32cad80_0 - typing-inspection=0.4.1=pyhd8ed1ab_0 @@ -292,7 +295,7 @@ dependencies: - zstandard=0.23.0=py310ha8f682b_2 - zstd=1.5.7=hbeecb71_2 - pip: - - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@3936709de7ce3cd7d469b6c88f34bb0a704670de + - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@ea323dc1e48634a19cca2b2693f989fae08880f1 - geoh5py @ git+https://github.com/MiraGeoscience/geoh5py.git@99695a5e34812bfbb53cef84803033a91af137de - mira-simpeg @ git+https://github.com/MiraGeoscience/simpeg.git@f72a1367edcb2da969002ca06f18f532340b3c27 - octree-creation-app @ git+https://github.com/MiraGeoscience/octree-creation-app.git@02fbd85bf7d54b8f4336f1f0094c1c3e27714e81 diff --git a/environments/py-3.10-win-64.conda.lock.yml b/environments/py-3.10-win-64.conda.lock.yml index 04d2a035..d9005f22 100644 --- a/environments/py-3.10-win-64.conda.lock.yml +++ b/environments/py-3.10-win-64.conda.lock.yml @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: win-64 -# input_hash: eaae335cf36adc80372dc46b8513b3371068cb4b78164f9bf33218ae884e5a43 +# input_hash: fe9bea64dc53fd535084a0a107d7688e8632e51eeb8084b72468b0a651a364b6 channels: - conda-forge @@ -67,6 +67,7 @@ dependencies: - liblapack=3.9.0=32_h1aa476e_mkl - liblzma=5.8.1=h2466b09_2 - libpng=1.6.49=h7a4582a_0 + - libspatialindex=2.0.0=h5a68840_0 - libsqlite=3.50.1=hf5d6505_7 - libssh2=1.11.1=h9aa295b_0 - libtiff=4.7.0=h05922d8_5 @@ -108,6 +109,7 @@ dependencies: - python_abi=3.10=7_cp310 - pytz=2025.2=pyhd8ed1ab_0 - pyyaml=6.0.2=py310h38315fa_2 + - rtree=1.2.0=py310h08d5ad2_1 - scikit-learn=1.4.2=py310hf2a6c47_1 - scipy=1.14.1=py310hbd0dde3_2 - setuptools=80.9.0=pyhff2d567_0 @@ -120,6 +122,7 @@ dependencies: - toolz=1.0.0=pyhd8ed1ab_1 - tornado=6.5.1=py310ha8f682b_0 - tqdm=4.67.1=pyhd8ed1ab_1 + - trimesh=4.1.8=pyhd8ed1ab_0 - typing-extensions=4.14.0=h32cad80_0 - typing-inspection=0.4.1=pyhd8ed1ab_0 - typing_extensions=4.14.0=pyhe01879c_0 @@ -142,7 +145,7 @@ dependencies: - zstandard=0.23.0=py310ha8f682b_2 - zstd=1.5.7=hbeecb71_2 - pip: - - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@3936709de7ce3cd7d469b6c88f34bb0a704670de + - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@ea323dc1e48634a19cca2b2693f989fae08880f1 - geoh5py @ git+https://github.com/MiraGeoscience/geoh5py.git@99695a5e34812bfbb53cef84803033a91af137de - mira-simpeg @ git+https://github.com/MiraGeoscience/simpeg.git@f72a1367edcb2da969002ca06f18f532340b3c27 - octree-creation-app @ git+https://github.com/MiraGeoscience/octree-creation-app.git@02fbd85bf7d54b8f4336f1f0094c1c3e27714e81 diff --git a/environments/py-3.11-linux-64-dev.conda.lock.yml b/environments/py-3.11-linux-64-dev.conda.lock.yml index a9df3638..cf762658 100644 --- a/environments/py-3.11-linux-64-dev.conda.lock.yml +++ b/environments/py-3.11-linux-64-dev.conda.lock.yml @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: c83206a2eacb4c3e0efe0aa15f924d81adc9fe1f02d077a12434b468bb2045e6 +# input_hash: c9dba1cefe5fafa1d6916df23312dff6e683994e65910cb3e402d7568b87973d channels: - conda-forge @@ -141,6 +141,7 @@ dependencies: - libpng=1.6.49=h943b412_0 - libscotch=7.0.6=hea33c07_1 - libsodium=1.0.20=h4ab18f5_0 + - libspatialindex=2.0.0=he02047a_0 - libsqlite=3.50.1=h6cd9bfd_7 - libssh2=1.11.1=hcf80075_0 - libstdcxx=15.1.0=h8f9b012_3 @@ -204,7 +205,7 @@ dependencies: - pthread-stubs=0.4=hb9d3cd8_1002 - ptyprocess=0.7.0=pyhd8ed1ab_1 - pure_eval=0.2.3=pyhd8ed1ab_1 - - pybtex=0.25.0=pyhd8ed1ab_1 + - pybtex=0.25.1=pyhd8ed1ab_0 - pybtex-docutils=1.0.3=py311h38be061_2 - pycparser=2.22=pyh29332c3_1 - pydantic=2.11.7=pyh3cfb1c2_0 @@ -235,6 +236,7 @@ dependencies: - rfc3339-validator=0.1.4=pyhd8ed1ab_1 - rfc3986-validator=0.1.1=pyh9f0ad1d_0 - rpds-py=0.25.1=py311hdae7d1d_0 + - rtree=1.2.0=py311ha1603b9_1 - scikit-learn=1.4.2=py311he08f58d_1 - scipy=1.14.1=py311he9a78e4_2 - send2trash=1.8.3=pyh0d859eb_1 @@ -277,6 +279,7 @@ dependencies: - tornado=6.5.1=py311h9ecbd09_0 - tqdm=4.67.1=pyhd8ed1ab_1 - traitlets=5.14.3=pyhd8ed1ab_1 + - trimesh=4.1.8=pyhd8ed1ab_0 - types-python-dateutil=2.9.0.20250516=pyhd8ed1ab_0 - typing-extensions=4.14.0=h32cad80_0 - typing-inspection=0.4.1=pyhd8ed1ab_0 @@ -305,7 +308,7 @@ dependencies: - zstandard=0.23.0=py311h9ecbd09_2 - zstd=1.5.7=hb8e6e7a_2 - pip: - - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@3936709de7ce3cd7d469b6c88f34bb0a704670de + - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@ea323dc1e48634a19cca2b2693f989fae08880f1 - geoh5py @ git+https://github.com/MiraGeoscience/geoh5py.git@99695a5e34812bfbb53cef84803033a91af137de - mira-simpeg @ git+https://github.com/MiraGeoscience/simpeg.git@f72a1367edcb2da969002ca06f18f532340b3c27 - octree-creation-app @ git+https://github.com/MiraGeoscience/octree-creation-app.git@02fbd85bf7d54b8f4336f1f0094c1c3e27714e81 diff --git a/environments/py-3.11-linux-64.conda.lock.yml b/environments/py-3.11-linux-64.conda.lock.yml index f138f120..d7db20af 100644 --- a/environments/py-3.11-linux-64.conda.lock.yml +++ b/environments/py-3.11-linux-64.conda.lock.yml @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: c83206a2eacb4c3e0efe0aa15f924d81adc9fe1f02d077a12434b468bb2045e6 +# input_hash: c9dba1cefe5fafa1d6916df23312dff6e683994e65910cb3e402d7568b87973d channels: - conda-forge @@ -78,6 +78,7 @@ dependencies: - libnsl=2.0.1=hb9d3cd8_1 - libpng=1.6.49=h943b412_0 - libscotch=7.0.6=hea33c07_1 + - libspatialindex=2.0.0=he02047a_0 - libsqlite=3.50.1=h6cd9bfd_7 - libssh2=1.11.1=hcf80075_0 - libstdcxx=15.1.0=h8f9b012_3 @@ -126,6 +127,7 @@ dependencies: - pytz=2025.2=pyhd8ed1ab_0 - pyyaml=6.0.2=py311h2dc5d0c_2 - readline=8.2=h8c095d6_2 + - rtree=1.2.0=py311ha1603b9_1 - scikit-learn=1.4.2=py311he08f58d_1 - scipy=1.14.1=py311he9a78e4_2 - setuptools=80.9.0=pyhff2d567_0 @@ -138,6 +140,7 @@ dependencies: - toolz=1.0.0=pyhd8ed1ab_1 - tornado=6.5.1=py311h9ecbd09_0 - tqdm=4.67.1=pyhd8ed1ab_1 + - trimesh=4.1.8=pyhd8ed1ab_0 - typing-extensions=4.14.0=h32cad80_0 - typing-inspection=0.4.1=pyhd8ed1ab_0 - typing_extensions=4.14.0=pyhe01879c_0 @@ -156,7 +159,7 @@ dependencies: - zstandard=0.23.0=py311h9ecbd09_2 - zstd=1.5.7=hb8e6e7a_2 - pip: - - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@3936709de7ce3cd7d469b6c88f34bb0a704670de + - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@ea323dc1e48634a19cca2b2693f989fae08880f1 - geoh5py @ git+https://github.com/MiraGeoscience/geoh5py.git@99695a5e34812bfbb53cef84803033a91af137de - mira-simpeg @ git+https://github.com/MiraGeoscience/simpeg.git@f72a1367edcb2da969002ca06f18f532340b3c27 - octree-creation-app @ git+https://github.com/MiraGeoscience/octree-creation-app.git@02fbd85bf7d54b8f4336f1f0094c1c3e27714e81 diff --git a/environments/py-3.11-win-64-dev.conda.lock.yml b/environments/py-3.11-win-64-dev.conda.lock.yml index db1a5137..c1f9b20a 100644 --- a/environments/py-3.11-win-64-dev.conda.lock.yml +++ b/environments/py-3.11-win-64-dev.conda.lock.yml @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: win-64 -# input_hash: 2d7c4e988ae2df2fbd8349e86ba264f7eb281505021cebbdb8d9a2d3b0dda7ad +# input_hash: b9f574bd52f02e9a0d8648f478d484cd228f132f708933856414eed155f61a30 channels: - conda-forge @@ -132,6 +132,7 @@ dependencies: - liblzma=5.8.1=h2466b09_2 - libpng=1.6.49=h7a4582a_0 - libsodium=1.0.20=hc70643c_0 + - libspatialindex=2.0.0=h5a68840_0 - libsqlite=3.50.1=hf5d6505_7 - libssh2=1.11.1=h9aa295b_0 - libtiff=4.7.0=h05922d8_5 @@ -187,7 +188,7 @@ dependencies: - psutil=7.0.0=py311he736701_0 - pthread-stubs=0.4=h0e40799_1002 - pure_eval=0.2.3=pyhd8ed1ab_1 - - pybtex=0.25.0=pyhd8ed1ab_1 + - pybtex=0.25.1=pyhd8ed1ab_0 - pybtex-docutils=1.0.3=py311h1ea47a8_2 - pycparser=2.22=pyh29332c3_1 - pydantic=2.11.7=pyh3cfb1c2_0 @@ -219,6 +220,7 @@ dependencies: - rfc3339-validator=0.1.4=pyhd8ed1ab_1 - rfc3986-validator=0.1.1=pyh9f0ad1d_0 - rpds-py=0.25.1=py311hc4022dc_0 + - rtree=1.2.0=py311h44d53c4_1 - scikit-learn=1.4.2=py311hdcb8d17_1 - scipy=1.14.1=py311hf16d85f_2 - send2trash=1.8.3=pyh5737063_1 @@ -261,6 +263,7 @@ dependencies: - tornado=6.5.1=py311he736701_0 - tqdm=4.67.1=pyhd8ed1ab_1 - traitlets=5.14.3=pyhd8ed1ab_1 + - trimesh=4.1.8=pyhd8ed1ab_0 - types-python-dateutil=2.9.0.20250516=pyhd8ed1ab_0 - typing-extensions=4.14.0=h32cad80_0 - typing-inspection=0.4.1=pyhd8ed1ab_0 @@ -295,7 +298,7 @@ dependencies: - zstandard=0.23.0=py311he736701_2 - zstd=1.5.7=hbeecb71_2 - pip: - - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@3936709de7ce3cd7d469b6c88f34bb0a704670de + - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@ea323dc1e48634a19cca2b2693f989fae08880f1 - geoh5py @ git+https://github.com/MiraGeoscience/geoh5py.git@99695a5e34812bfbb53cef84803033a91af137de - mira-simpeg @ git+https://github.com/MiraGeoscience/simpeg.git@f72a1367edcb2da969002ca06f18f532340b3c27 - octree-creation-app @ git+https://github.com/MiraGeoscience/octree-creation-app.git@02fbd85bf7d54b8f4336f1f0094c1c3e27714e81 diff --git a/environments/py-3.11-win-64.conda.lock.yml b/environments/py-3.11-win-64.conda.lock.yml index cbf756fd..9023c56a 100644 --- a/environments/py-3.11-win-64.conda.lock.yml +++ b/environments/py-3.11-win-64.conda.lock.yml @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: win-64 -# input_hash: 2d7c4e988ae2df2fbd8349e86ba264f7eb281505021cebbdb8d9a2d3b0dda7ad +# input_hash: b9f574bd52f02e9a0d8648f478d484cd228f132f708933856414eed155f61a30 channels: - conda-forge @@ -68,6 +68,7 @@ dependencies: - liblapack=3.9.0=32_h1aa476e_mkl - liblzma=5.8.1=h2466b09_2 - libpng=1.6.49=h7a4582a_0 + - libspatialindex=2.0.0=h5a68840_0 - libsqlite=3.50.1=hf5d6505_7 - libssh2=1.11.1=h9aa295b_0 - libtiff=4.7.0=h05922d8_5 @@ -109,6 +110,7 @@ dependencies: - python_abi=3.11=7_cp311 - pytz=2025.2=pyhd8ed1ab_0 - pyyaml=6.0.2=py311h5082efb_2 + - rtree=1.2.0=py311h44d53c4_1 - scikit-learn=1.4.2=py311hdcb8d17_1 - scipy=1.14.1=py311hf16d85f_2 - setuptools=80.9.0=pyhff2d567_0 @@ -121,6 +123,7 @@ dependencies: - toolz=1.0.0=pyhd8ed1ab_1 - tornado=6.5.1=py311he736701_0 - tqdm=4.67.1=pyhd8ed1ab_1 + - trimesh=4.1.8=pyhd8ed1ab_0 - typing-extensions=4.14.0=h32cad80_0 - typing-inspection=0.4.1=pyhd8ed1ab_0 - typing_extensions=4.14.0=pyhe01879c_0 @@ -144,7 +147,7 @@ dependencies: - zstandard=0.23.0=py311he736701_2 - zstd=1.5.7=hbeecb71_2 - pip: - - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@3936709de7ce3cd7d469b6c88f34bb0a704670de + - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@ea323dc1e48634a19cca2b2693f989fae08880f1 - geoh5py @ git+https://github.com/MiraGeoscience/geoh5py.git@99695a5e34812bfbb53cef84803033a91af137de - mira-simpeg @ git+https://github.com/MiraGeoscience/simpeg.git@f72a1367edcb2da969002ca06f18f532340b3c27 - octree-creation-app @ git+https://github.com/MiraGeoscience/octree-creation-app.git@02fbd85bf7d54b8f4336f1f0094c1c3e27714e81 diff --git a/environments/py-3.12-linux-64-dev.conda.lock.yml b/environments/py-3.12-linux-64-dev.conda.lock.yml index 812b151d..f3004131 100644 --- a/environments/py-3.12-linux-64-dev.conda.lock.yml +++ b/environments/py-3.12-linux-64-dev.conda.lock.yml @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 86d8c36c4b440eac7a217d19ba6775b033719c3606e19d205f30e9bf039ae01e +# input_hash: 986de85ed6e6464ef1d28bc0bd68959524a00d49b9d9e57fda77fdcf3447934a channels: - conda-forge @@ -141,6 +141,7 @@ dependencies: - libpng=1.6.49=h943b412_0 - libscotch=7.0.6=hea33c07_1 - libsodium=1.0.20=h4ab18f5_0 + - libspatialindex=2.0.0=he02047a_0 - libsqlite=3.50.1=h6cd9bfd_7 - libssh2=1.11.1=hcf80075_0 - libstdcxx=15.1.0=h8f9b012_3 @@ -204,7 +205,7 @@ dependencies: - pthread-stubs=0.4=hb9d3cd8_1002 - ptyprocess=0.7.0=pyhd8ed1ab_1 - pure_eval=0.2.3=pyhd8ed1ab_1 - - pybtex=0.25.0=pyhd8ed1ab_1 + - pybtex=0.25.1=pyhd8ed1ab_0 - pybtex-docutils=1.0.3=py312h7900ff3_2 - pycparser=2.22=pyh29332c3_1 - pydantic=2.11.7=pyh3cfb1c2_0 @@ -235,6 +236,7 @@ dependencies: - rfc3339-validator=0.1.4=pyhd8ed1ab_1 - rfc3986-validator=0.1.1=pyh9f0ad1d_0 - rpds-py=0.25.1=py312h680f630_0 + - rtree=1.2.0=py312h3ed4c40_1 - scikit-learn=1.4.2=py312h1fcc3ea_1 - scipy=1.14.1=py312h62794b6_2 - send2trash=1.8.3=pyh0d859eb_1 @@ -277,6 +279,7 @@ dependencies: - tornado=6.5.1=py312h66e93f0_0 - tqdm=4.67.1=pyhd8ed1ab_1 - traitlets=5.14.3=pyhd8ed1ab_1 + - trimesh=4.1.8=pyhd8ed1ab_0 - types-python-dateutil=2.9.0.20250516=pyhd8ed1ab_0 - typing-extensions=4.14.0=h32cad80_0 - typing-inspection=0.4.1=pyhd8ed1ab_0 @@ -305,7 +308,7 @@ dependencies: - zstandard=0.23.0=py312h66e93f0_2 - zstd=1.5.7=hb8e6e7a_2 - pip: - - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@3936709de7ce3cd7d469b6c88f34bb0a704670de + - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@ea323dc1e48634a19cca2b2693f989fae08880f1 - geoh5py @ git+https://github.com/MiraGeoscience/geoh5py.git@99695a5e34812bfbb53cef84803033a91af137de - mira-simpeg @ git+https://github.com/MiraGeoscience/simpeg.git@f72a1367edcb2da969002ca06f18f532340b3c27 - octree-creation-app @ git+https://github.com/MiraGeoscience/octree-creation-app.git@02fbd85bf7d54b8f4336f1f0094c1c3e27714e81 diff --git a/environments/py-3.12-linux-64.conda.lock.yml b/environments/py-3.12-linux-64.conda.lock.yml index 9d3dec15..67fe8f49 100644 --- a/environments/py-3.12-linux-64.conda.lock.yml +++ b/environments/py-3.12-linux-64.conda.lock.yml @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 86d8c36c4b440eac7a217d19ba6775b033719c3606e19d205f30e9bf039ae01e +# input_hash: 986de85ed6e6464ef1d28bc0bd68959524a00d49b9d9e57fda77fdcf3447934a channels: - conda-forge @@ -78,6 +78,7 @@ dependencies: - libnsl=2.0.1=hb9d3cd8_1 - libpng=1.6.49=h943b412_0 - libscotch=7.0.6=hea33c07_1 + - libspatialindex=2.0.0=he02047a_0 - libsqlite=3.50.1=h6cd9bfd_7 - libssh2=1.11.1=hcf80075_0 - libstdcxx=15.1.0=h8f9b012_3 @@ -126,6 +127,7 @@ dependencies: - pytz=2025.2=pyhd8ed1ab_0 - pyyaml=6.0.2=py312h178313f_2 - readline=8.2=h8c095d6_2 + - rtree=1.2.0=py312h3ed4c40_1 - scikit-learn=1.4.2=py312h1fcc3ea_1 - scipy=1.14.1=py312h62794b6_2 - setuptools=80.9.0=pyhff2d567_0 @@ -138,6 +140,7 @@ dependencies: - toolz=1.0.0=pyhd8ed1ab_1 - tornado=6.5.1=py312h66e93f0_0 - tqdm=4.67.1=pyhd8ed1ab_1 + - trimesh=4.1.8=pyhd8ed1ab_0 - typing-extensions=4.14.0=h32cad80_0 - typing-inspection=0.4.1=pyhd8ed1ab_0 - typing_extensions=4.14.0=pyhe01879c_0 @@ -156,7 +159,7 @@ dependencies: - zstandard=0.23.0=py312h66e93f0_2 - zstd=1.5.7=hb8e6e7a_2 - pip: - - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@3936709de7ce3cd7d469b6c88f34bb0a704670de + - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@ea323dc1e48634a19cca2b2693f989fae08880f1 - geoh5py @ git+https://github.com/MiraGeoscience/geoh5py.git@99695a5e34812bfbb53cef84803033a91af137de - mira-simpeg @ git+https://github.com/MiraGeoscience/simpeg.git@f72a1367edcb2da969002ca06f18f532340b3c27 - octree-creation-app @ git+https://github.com/MiraGeoscience/octree-creation-app.git@02fbd85bf7d54b8f4336f1f0094c1c3e27714e81 diff --git a/environments/py-3.12-win-64-dev.conda.lock.yml b/environments/py-3.12-win-64-dev.conda.lock.yml index b13d5e8d..9887e657 100644 --- a/environments/py-3.12-win-64-dev.conda.lock.yml +++ b/environments/py-3.12-win-64-dev.conda.lock.yml @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: win-64 -# input_hash: dd5f3d6403d5db52a5e41fb35b60dce2df1a615f984bba47e191a2a14a68dd3c +# input_hash: 8ac7e9b31d0a8968577d04b18bfae4e5b55625cc38cfb3a62f6b8f87f8a156f9 channels: - conda-forge @@ -132,6 +132,7 @@ dependencies: - liblzma=5.8.1=h2466b09_2 - libpng=1.6.49=h7a4582a_0 - libsodium=1.0.20=hc70643c_0 + - libspatialindex=2.0.0=h5a68840_0 - libsqlite=3.50.1=hf5d6505_7 - libssh2=1.11.1=h9aa295b_0 - libtiff=4.7.0=h05922d8_5 @@ -187,7 +188,7 @@ dependencies: - psutil=7.0.0=py312h4389bb4_0 - pthread-stubs=0.4=h0e40799_1002 - pure_eval=0.2.3=pyhd8ed1ab_1 - - pybtex=0.25.0=pyhd8ed1ab_1 + - pybtex=0.25.1=pyhd8ed1ab_0 - pybtex-docutils=1.0.3=py312h2e8e312_2 - pycparser=2.22=pyh29332c3_1 - pydantic=2.11.7=pyh3cfb1c2_0 @@ -219,6 +220,7 @@ dependencies: - rfc3339-validator=0.1.4=pyhd8ed1ab_1 - rfc3986-validator=0.1.1=pyh9f0ad1d_0 - rpds-py=0.25.1=py312h8422cdd_0 + - rtree=1.2.0=py312h50e5f8f_1 - scikit-learn=1.4.2=py312h816cc57_1 - scipy=1.14.1=py312h337df96_2 - send2trash=1.8.3=pyh5737063_1 @@ -261,6 +263,7 @@ dependencies: - tornado=6.5.1=py312h4389bb4_0 - tqdm=4.67.1=pyhd8ed1ab_1 - traitlets=5.14.3=pyhd8ed1ab_1 + - trimesh=4.1.8=pyhd8ed1ab_0 - types-python-dateutil=2.9.0.20250516=pyhd8ed1ab_0 - typing-extensions=4.14.0=h32cad80_0 - typing-inspection=0.4.1=pyhd8ed1ab_0 @@ -295,7 +298,7 @@ dependencies: - zstandard=0.23.0=py312h4389bb4_2 - zstd=1.5.7=hbeecb71_2 - pip: - - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@3936709de7ce3cd7d469b6c88f34bb0a704670de + - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@ea323dc1e48634a19cca2b2693f989fae08880f1 - geoh5py @ git+https://github.com/MiraGeoscience/geoh5py.git@99695a5e34812bfbb53cef84803033a91af137de - mira-simpeg @ git+https://github.com/MiraGeoscience/simpeg.git@f72a1367edcb2da969002ca06f18f532340b3c27 - octree-creation-app @ git+https://github.com/MiraGeoscience/octree-creation-app.git@02fbd85bf7d54b8f4336f1f0094c1c3e27714e81 diff --git a/environments/py-3.12-win-64.conda.lock.yml b/environments/py-3.12-win-64.conda.lock.yml index 53e68fd2..fdf241ec 100644 --- a/environments/py-3.12-win-64.conda.lock.yml +++ b/environments/py-3.12-win-64.conda.lock.yml @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: win-64 -# input_hash: dd5f3d6403d5db52a5e41fb35b60dce2df1a615f984bba47e191a2a14a68dd3c +# input_hash: 8ac7e9b31d0a8968577d04b18bfae4e5b55625cc38cfb3a62f6b8f87f8a156f9 channels: - conda-forge @@ -68,6 +68,7 @@ dependencies: - liblapack=3.9.0=32_h1aa476e_mkl - liblzma=5.8.1=h2466b09_2 - libpng=1.6.49=h7a4582a_0 + - libspatialindex=2.0.0=h5a68840_0 - libsqlite=3.50.1=hf5d6505_7 - libssh2=1.11.1=h9aa295b_0 - libtiff=4.7.0=h05922d8_5 @@ -109,6 +110,7 @@ dependencies: - python_abi=3.12=7_cp312 - pytz=2025.2=pyhd8ed1ab_0 - pyyaml=6.0.2=py312h31fea79_2 + - rtree=1.2.0=py312h50e5f8f_1 - scikit-learn=1.4.2=py312h816cc57_1 - scipy=1.14.1=py312h337df96_2 - setuptools=80.9.0=pyhff2d567_0 @@ -121,6 +123,7 @@ dependencies: - toolz=1.0.0=pyhd8ed1ab_1 - tornado=6.5.1=py312h4389bb4_0 - tqdm=4.67.1=pyhd8ed1ab_1 + - trimesh=4.1.8=pyhd8ed1ab_0 - typing-extensions=4.14.0=h32cad80_0 - typing-inspection=0.4.1=pyhd8ed1ab_0 - typing_extensions=4.14.0=pyhe01879c_0 @@ -144,7 +147,7 @@ dependencies: - zstandard=0.23.0=py312h4389bb4_2 - zstd=1.5.7=hbeecb71_2 - pip: - - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@3936709de7ce3cd7d469b6c88f34bb0a704670de + - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@ea323dc1e48634a19cca2b2693f989fae08880f1 - geoh5py @ git+https://github.com/MiraGeoscience/geoh5py.git@99695a5e34812bfbb53cef84803033a91af137de - mira-simpeg @ git+https://github.com/MiraGeoscience/simpeg.git@f72a1367edcb2da969002ca06f18f532340b3c27 - octree-creation-app @ git+https://github.com/MiraGeoscience/octree-creation-app.git@02fbd85bf7d54b8f4336f1f0094c1c3e27714e81 diff --git a/py-3.10.conda-lock.yml b/py-3.10.conda-lock.yml index b1b99aec..3600c425 100644 --- a/py-3.10.conda-lock.yml +++ b/py-3.10.conda-lock.yml @@ -15,8 +15,8 @@ version: 1 metadata: content_hash: - win-64: eaae335cf36adc80372dc46b8513b3371068cb4b78164f9bf33218ae884e5a43 - linux-64: d20cb4f62773f79495ca5432e662a34453afa41e6084f06c349fbb84600a44bb + win-64: fe9bea64dc53fd535084a0a107d7688e8632e51eeb8084b72468b0a651a364b6 + linux-64: 0f54a26695792bef9ae71d11e72896171987869d09020c611e73c9a0622596d6 channels: - url: conda-forge used_env_vars: [] @@ -3826,6 +3826,34 @@ package: sha256: 7bcb3edccea30f711b6be9601e083ecf4f435b9407d70fc48fbcf9e5d69a0fc6 category: dev optional: true +- name: libspatialindex + version: 2.0.0 + manager: conda + platform: linux-64 + dependencies: + __glibc: '>=2.17,<3.0.a0' + libgcc-ng: '>=12' + libstdcxx-ng: '>=12' + url: https://repo.prefix.dev/conda-forge/linux-64/libspatialindex-2.0.0-he02047a_0.conda + hash: + md5: e7d2dcd1a058149ff9731a8dca39566e + sha256: 997a4fa13864dcb35ac9dfe87ed70fb3e9509dd071fa1951ac7f184e7ffcde5d + category: main + optional: false +- name: libspatialindex + version: 2.0.0 + manager: conda + platform: win-64 + dependencies: + ucrt: '>=10.0.20348.0' + vc: '>=14.2,<15' + vc14_runtime: '>=14.29.30139' + url: https://repo.prefix.dev/conda-forge/win-64/libspatialindex-2.0.0-h5a68840_0.conda + hash: + md5: 667559340fdf805ee1652de7b73e2b59 + sha256: 7802e6c51d59bc7e062841c525d772656708cdc44e42b6556493d345f08d7e50 + category: main + optional: false - name: libsqlite version: 3.50.1 manager: conda @@ -5571,7 +5599,7 @@ package: category: dev optional: true - name: pybtex - version: 0.25.0 + version: 0.25.1 manager: conda platform: linux-64 dependencies: @@ -5580,14 +5608,14 @@ package: python: '>=3.9' pyyaml: '>=3.01' setuptools: '' - url: https://repo.prefix.dev/conda-forge/noarch/pybtex-0.25.0-pyhd8ed1ab_1.conda + url: https://repo.prefix.dev/conda-forge/noarch/pybtex-0.25.1-pyhd8ed1ab_0.conda hash: - md5: 78b5c915435130d04d1f1bf3d9d42543 - sha256: 8a59892ecdc3e76e05f4a448c378bd1d6ba671f5fcab0ad9c628ab6a96f48d84 + md5: 9c25a850410220d31085173fbfdfa191 + sha256: 3053895e08ce56923e48eea7d1c07a6d8bf09948d1e69a21ae7ab9e459b0a227 category: dev optional: true - name: pybtex - version: 0.25.0 + version: 0.25.1 manager: conda platform: win-64 dependencies: @@ -5596,10 +5624,10 @@ package: python: '>=3.9' pyyaml: '>=3.01' setuptools: '' - url: https://repo.prefix.dev/conda-forge/noarch/pybtex-0.25.0-pyhd8ed1ab_1.conda + url: https://repo.prefix.dev/conda-forge/noarch/pybtex-0.25.1-pyhd8ed1ab_0.conda hash: - md5: 78b5c915435130d04d1f1bf3d9d42543 - sha256: 8a59892ecdc3e76e05f4a448c378bd1d6ba671f5fcab0ad9c628ab6a96f48d84 + md5: 9c25a850410220d31085173fbfdfa191 + sha256: 3053895e08ce56923e48eea7d1c07a6d8bf09948d1e69a21ae7ab9e459b0a227 category: dev optional: true - name: pybtex-docutils @@ -6541,6 +6569,34 @@ package: sha256: ef0525e1c1cda25d4455d2ab8935673a71b49b9a2d5c152cdbfc765ad091dd53 category: dev optional: true +- name: rtree + version: 1.2.0 + manager: conda + platform: linux-64 + dependencies: + libspatialindex: '>=2.0.0,<2.0.1.0a0' + python: '>=3.10,<3.11.0a0' + python_abi: 3.10.* + url: https://repo.prefix.dev/conda-forge/linux-64/rtree-1.2.0-py310haf1e407_1.conda + hash: + md5: aab35e5bbaac5bc7057effffe2b55df8 + sha256: 6eb76990124941e5303eb739b2ab8684112f829b6bfafc81b43bd722c3c91616 + category: main + optional: false +- name: rtree + version: 1.2.0 + manager: conda + platform: win-64 + dependencies: + libspatialindex: '>=2.0.0,<2.0.1.0a0' + python: '>=3.10,<3.11.0a0' + python_abi: 3.10.* + url: https://repo.prefix.dev/conda-forge/win-64/rtree-1.2.0-py310h08d5ad2_1.conda + hash: + md5: ffc97287567416c807a69aeeee794678 + sha256: 5c1dbc4390adc6a75e2c648761b9e5e2a70ec1fab4e4055fed64bb3852604ace + category: main + optional: false - name: scikit-learn version: 1.4.2 manager: conda @@ -7714,6 +7770,32 @@ package: sha256: f39a5620c6e8e9e98357507262a7869de2ae8cc07da8b7f84e517c9fd6c2b959 category: dev optional: true +- name: trimesh + version: 4.1.8 + manager: conda + platform: linux-64 + dependencies: + numpy: '' + python: '>=2.7' + url: https://repo.prefix.dev/conda-forge/noarch/trimesh-4.1.8-pyhd8ed1ab_0.conda + hash: + md5: 78302527eb6c9d18b07a91e6a72ef957 + sha256: 021110c37eca2f0fca85ba6ac4576c509d23079758f63942e2f9a6954282f2ce + category: main + optional: false +- name: trimesh + version: 4.1.8 + manager: conda + platform: win-64 + dependencies: + numpy: '' + python: '>=2.7' + url: https://repo.prefix.dev/conda-forge/noarch/trimesh-4.1.8-pyhd8ed1ab_0.conda + hash: + md5: 78302527eb6c9d18b07a91e6a72ef957 + sha256: 021110c37eca2f0fca85ba6ac4576c509d23079758f63942e2f9a6954282f2ce + category: main + optional: false - name: types-python-dateutil version: 2.9.0.20250516 manager: conda @@ -8473,12 +8555,12 @@ package: numpy: '>=1.26.0,<1.27.0' pydantic: '>=2.5.2,<3.0.0' scipy: '>=1.14.0,<1.15.0' - url: git+https://github.com/MiraGeoscience/geoapps-utils.git@3936709de7ce3cd7d469b6c88f34bb0a704670de + url: git+https://github.com/MiraGeoscience/geoapps-utils.git@ea323dc1e48634a19cca2b2693f989fae08880f1 hash: - sha256: 3936709de7ce3cd7d469b6c88f34bb0a704670de + sha256: ea323dc1e48634a19cca2b2693f989fae08880f1 source: type: url - url: git+https://github.com/MiraGeoscience/geoapps-utils.git@3936709de7ce3cd7d469b6c88f34bb0a704670de + url: git+https://github.com/MiraGeoscience/geoapps-utils.git@ea323dc1e48634a19cca2b2693f989fae08880f1 category: main optional: false - name: geoapps-utils @@ -8490,12 +8572,12 @@ package: numpy: '>=1.26.0,<1.27.0' pydantic: '>=2.5.2,<3.0.0' scipy: '>=1.14.0,<1.15.0' - url: git+https://github.com/MiraGeoscience/geoapps-utils.git@3936709de7ce3cd7d469b6c88f34bb0a704670de + url: git+https://github.com/MiraGeoscience/geoapps-utils.git@ea323dc1e48634a19cca2b2693f989fae08880f1 hash: - sha256: 3936709de7ce3cd7d469b6c88f34bb0a704670de + sha256: ea323dc1e48634a19cca2b2693f989fae08880f1 source: type: url - url: git+https://github.com/MiraGeoscience/geoapps-utils.git@3936709de7ce3cd7d469b6c88f34bb0a704670de + url: git+https://github.com/MiraGeoscience/geoapps-utils.git@ea323dc1e48634a19cca2b2693f989fae08880f1 category: main optional: false - name: geoh5py diff --git a/py-3.11.conda-lock.yml b/py-3.11.conda-lock.yml index 78d4faee..c434bda3 100644 --- a/py-3.11.conda-lock.yml +++ b/py-3.11.conda-lock.yml @@ -15,8 +15,8 @@ version: 1 metadata: content_hash: - win-64: 2d7c4e988ae2df2fbd8349e86ba264f7eb281505021cebbdb8d9a2d3b0dda7ad - linux-64: c83206a2eacb4c3e0efe0aa15f924d81adc9fe1f02d077a12434b468bb2045e6 + win-64: b9f574bd52f02e9a0d8648f478d484cd228f132f708933856414eed155f61a30 + linux-64: c9dba1cefe5fafa1d6916df23312dff6e683994e65910cb3e402d7568b87973d channels: - url: conda-forge used_env_vars: [] @@ -3878,6 +3878,34 @@ package: sha256: 7bcb3edccea30f711b6be9601e083ecf4f435b9407d70fc48fbcf9e5d69a0fc6 category: dev optional: true +- name: libspatialindex + version: 2.0.0 + manager: conda + platform: linux-64 + dependencies: + __glibc: '>=2.17,<3.0.a0' + libgcc-ng: '>=12' + libstdcxx-ng: '>=12' + url: https://repo.prefix.dev/conda-forge/linux-64/libspatialindex-2.0.0-he02047a_0.conda + hash: + md5: e7d2dcd1a058149ff9731a8dca39566e + sha256: 997a4fa13864dcb35ac9dfe87ed70fb3e9509dd071fa1951ac7f184e7ffcde5d + category: main + optional: false +- name: libspatialindex + version: 2.0.0 + manager: conda + platform: win-64 + dependencies: + ucrt: '>=10.0.20348.0' + vc: '>=14.2,<15' + vc14_runtime: '>=14.29.30139' + url: https://repo.prefix.dev/conda-forge/win-64/libspatialindex-2.0.0-h5a68840_0.conda + hash: + md5: 667559340fdf805ee1652de7b73e2b59 + sha256: 7802e6c51d59bc7e062841c525d772656708cdc44e42b6556493d345f08d7e50 + category: main + optional: false - name: libsqlite version: 3.50.1 manager: conda @@ -5625,7 +5653,7 @@ package: category: dev optional: true - name: pybtex - version: 0.25.0 + version: 0.25.1 manager: conda platform: linux-64 dependencies: @@ -5634,14 +5662,14 @@ package: python: '>=3.9' pyyaml: '>=3.01' setuptools: '' - url: https://repo.prefix.dev/conda-forge/noarch/pybtex-0.25.0-pyhd8ed1ab_1.conda + url: https://repo.prefix.dev/conda-forge/noarch/pybtex-0.25.1-pyhd8ed1ab_0.conda hash: - md5: 78b5c915435130d04d1f1bf3d9d42543 - sha256: 8a59892ecdc3e76e05f4a448c378bd1d6ba671f5fcab0ad9c628ab6a96f48d84 + md5: 9c25a850410220d31085173fbfdfa191 + sha256: 3053895e08ce56923e48eea7d1c07a6d8bf09948d1e69a21ae7ab9e459b0a227 category: dev optional: true - name: pybtex - version: 0.25.0 + version: 0.25.1 manager: conda platform: win-64 dependencies: @@ -5650,10 +5678,10 @@ package: python: '>=3.9' pyyaml: '>=3.01' setuptools: '' - url: https://repo.prefix.dev/conda-forge/noarch/pybtex-0.25.0-pyhd8ed1ab_1.conda + url: https://repo.prefix.dev/conda-forge/noarch/pybtex-0.25.1-pyhd8ed1ab_0.conda hash: - md5: 78b5c915435130d04d1f1bf3d9d42543 - sha256: 8a59892ecdc3e76e05f4a448c378bd1d6ba671f5fcab0ad9c628ab6a96f48d84 + md5: 9c25a850410220d31085173fbfdfa191 + sha256: 3053895e08ce56923e48eea7d1c07a6d8bf09948d1e69a21ae7ab9e459b0a227 category: dev optional: true - name: pybtex-docutils @@ -6595,6 +6623,34 @@ package: sha256: 3a76edb8f446351f36eb43a215e0df0b444f73b0f22453c0966611653b05c06f category: dev optional: true +- name: rtree + version: 1.2.0 + manager: conda + platform: linux-64 + dependencies: + libspatialindex: '>=2.0.0,<2.0.1.0a0' + python: '>=3.11,<3.12.0a0' + python_abi: 3.11.* + url: https://repo.prefix.dev/conda-forge/linux-64/rtree-1.2.0-py311ha1603b9_1.conda + hash: + md5: 0737315cc9761f4060f9d52d12cea92e + sha256: 9b9d5be1924ced85110f635331379354ba57d44c5416c5709070ddb111048ef6 + category: main + optional: false +- name: rtree + version: 1.2.0 + manager: conda + platform: win-64 + dependencies: + libspatialindex: '>=2.0.0,<2.0.1.0a0' + python: '>=3.11,<3.12.0a0' + python_abi: 3.11.* + url: https://repo.prefix.dev/conda-forge/win-64/rtree-1.2.0-py311h44d53c4_1.conda + hash: + md5: a182e3a376af719a275136bfdbc3a70e + sha256: 78fecaad4f4b25ba60dc55af7fb5326d1b3512b8ed240eb45aabc1e86e50e77e + category: main + optional: false - name: scikit-learn version: 1.4.2 manager: conda @@ -7768,6 +7824,32 @@ package: sha256: f39a5620c6e8e9e98357507262a7869de2ae8cc07da8b7f84e517c9fd6c2b959 category: dev optional: true +- name: trimesh + version: 4.1.8 + manager: conda + platform: linux-64 + dependencies: + numpy: '' + python: '>=2.7' + url: https://repo.prefix.dev/conda-forge/noarch/trimesh-4.1.8-pyhd8ed1ab_0.conda + hash: + md5: 78302527eb6c9d18b07a91e6a72ef957 + sha256: 021110c37eca2f0fca85ba6ac4576c509d23079758f63942e2f9a6954282f2ce + category: main + optional: false +- name: trimesh + version: 4.1.8 + manager: conda + platform: win-64 + dependencies: + numpy: '' + python: '>=2.7' + url: https://repo.prefix.dev/conda-forge/noarch/trimesh-4.1.8-pyhd8ed1ab_0.conda + hash: + md5: 78302527eb6c9d18b07a91e6a72ef957 + sha256: 021110c37eca2f0fca85ba6ac4576c509d23079758f63942e2f9a6954282f2ce + category: main + optional: false - name: types-python-dateutil version: 2.9.0.20250516 manager: conda @@ -8558,12 +8640,12 @@ package: numpy: '>=1.26.0,<1.27.0' pydantic: '>=2.5.2,<3.0.0' scipy: '>=1.14.0,<1.15.0' - url: git+https://github.com/MiraGeoscience/geoapps-utils.git@3936709de7ce3cd7d469b6c88f34bb0a704670de + url: git+https://github.com/MiraGeoscience/geoapps-utils.git@ea323dc1e48634a19cca2b2693f989fae08880f1 hash: - sha256: 3936709de7ce3cd7d469b6c88f34bb0a704670de + sha256: ea323dc1e48634a19cca2b2693f989fae08880f1 source: type: url - url: git+https://github.com/MiraGeoscience/geoapps-utils.git@3936709de7ce3cd7d469b6c88f34bb0a704670de + url: git+https://github.com/MiraGeoscience/geoapps-utils.git@ea323dc1e48634a19cca2b2693f989fae08880f1 category: main optional: false - name: geoapps-utils @@ -8575,12 +8657,12 @@ package: numpy: '>=1.26.0,<1.27.0' pydantic: '>=2.5.2,<3.0.0' scipy: '>=1.14.0,<1.15.0' - url: git+https://github.com/MiraGeoscience/geoapps-utils.git@3936709de7ce3cd7d469b6c88f34bb0a704670de + url: git+https://github.com/MiraGeoscience/geoapps-utils.git@ea323dc1e48634a19cca2b2693f989fae08880f1 hash: - sha256: 3936709de7ce3cd7d469b6c88f34bb0a704670de + sha256: ea323dc1e48634a19cca2b2693f989fae08880f1 source: type: url - url: git+https://github.com/MiraGeoscience/geoapps-utils.git@3936709de7ce3cd7d469b6c88f34bb0a704670de + url: git+https://github.com/MiraGeoscience/geoapps-utils.git@ea323dc1e48634a19cca2b2693f989fae08880f1 category: main optional: false - name: geoh5py diff --git a/py-3.12.conda-lock.yml b/py-3.12.conda-lock.yml index d74f7385..08f0e359 100644 --- a/py-3.12.conda-lock.yml +++ b/py-3.12.conda-lock.yml @@ -15,8 +15,8 @@ version: 1 metadata: content_hash: - win-64: dd5f3d6403d5db52a5e41fb35b60dce2df1a615f984bba47e191a2a14a68dd3c - linux-64: 86d8c36c4b440eac7a217d19ba6775b033719c3606e19d205f30e9bf039ae01e + win-64: 8ac7e9b31d0a8968577d04b18bfae4e5b55625cc38cfb3a62f6b8f87f8a156f9 + linux-64: 986de85ed6e6464ef1d28bc0bd68959524a00d49b9d9e57fda77fdcf3447934a channels: - url: conda-forge used_env_vars: [] @@ -3878,6 +3878,34 @@ package: sha256: 7bcb3edccea30f711b6be9601e083ecf4f435b9407d70fc48fbcf9e5d69a0fc6 category: dev optional: true +- name: libspatialindex + version: 2.0.0 + manager: conda + platform: linux-64 + dependencies: + __glibc: '>=2.17,<3.0.a0' + libgcc-ng: '>=12' + libstdcxx-ng: '>=12' + url: https://repo.prefix.dev/conda-forge/linux-64/libspatialindex-2.0.0-he02047a_0.conda + hash: + md5: e7d2dcd1a058149ff9731a8dca39566e + sha256: 997a4fa13864dcb35ac9dfe87ed70fb3e9509dd071fa1951ac7f184e7ffcde5d + category: main + optional: false +- name: libspatialindex + version: 2.0.0 + manager: conda + platform: win-64 + dependencies: + ucrt: '>=10.0.20348.0' + vc: '>=14.2,<15' + vc14_runtime: '>=14.29.30139' + url: https://repo.prefix.dev/conda-forge/win-64/libspatialindex-2.0.0-h5a68840_0.conda + hash: + md5: 667559340fdf805ee1652de7b73e2b59 + sha256: 7802e6c51d59bc7e062841c525d772656708cdc44e42b6556493d345f08d7e50 + category: main + optional: false - name: libsqlite version: 3.50.1 manager: conda @@ -5625,7 +5653,7 @@ package: category: dev optional: true - name: pybtex - version: 0.25.0 + version: 0.25.1 manager: conda platform: linux-64 dependencies: @@ -5634,14 +5662,14 @@ package: python: '>=3.9' pyyaml: '>=3.01' setuptools: '' - url: https://repo.prefix.dev/conda-forge/noarch/pybtex-0.25.0-pyhd8ed1ab_1.conda + url: https://repo.prefix.dev/conda-forge/noarch/pybtex-0.25.1-pyhd8ed1ab_0.conda hash: - md5: 78b5c915435130d04d1f1bf3d9d42543 - sha256: 8a59892ecdc3e76e05f4a448c378bd1d6ba671f5fcab0ad9c628ab6a96f48d84 + md5: 9c25a850410220d31085173fbfdfa191 + sha256: 3053895e08ce56923e48eea7d1c07a6d8bf09948d1e69a21ae7ab9e459b0a227 category: dev optional: true - name: pybtex - version: 0.25.0 + version: 0.25.1 manager: conda platform: win-64 dependencies: @@ -5650,10 +5678,10 @@ package: python: '>=3.9' pyyaml: '>=3.01' setuptools: '' - url: https://repo.prefix.dev/conda-forge/noarch/pybtex-0.25.0-pyhd8ed1ab_1.conda + url: https://repo.prefix.dev/conda-forge/noarch/pybtex-0.25.1-pyhd8ed1ab_0.conda hash: - md5: 78b5c915435130d04d1f1bf3d9d42543 - sha256: 8a59892ecdc3e76e05f4a448c378bd1d6ba671f5fcab0ad9c628ab6a96f48d84 + md5: 9c25a850410220d31085173fbfdfa191 + sha256: 3053895e08ce56923e48eea7d1c07a6d8bf09948d1e69a21ae7ab9e459b0a227 category: dev optional: true - name: pybtex-docutils @@ -6595,6 +6623,34 @@ package: sha256: dfea71a35d7d5eb348893e24136ce6fb1004fc9402eaafae441fa61887638764 category: dev optional: true +- name: rtree + version: 1.2.0 + manager: conda + platform: linux-64 + dependencies: + libspatialindex: '>=2.0.0,<2.0.1.0a0' + python: '>=3.12,<3.13.0a0' + python_abi: 3.12.* + url: https://repo.prefix.dev/conda-forge/linux-64/rtree-1.2.0-py312h3ed4c40_1.conda + hash: + md5: 99780d5aa94447bc17298a22565ad592 + sha256: 2936fc466bac7dd43b80072440b2daaa1e76db504e2218b76a4e3b7528acb196 + category: main + optional: false +- name: rtree + version: 1.2.0 + manager: conda + platform: win-64 + dependencies: + libspatialindex: '>=2.0.0,<2.0.1.0a0' + python: '>=3.12,<3.13.0a0' + python_abi: 3.12.* + url: https://repo.prefix.dev/conda-forge/win-64/rtree-1.2.0-py312h50e5f8f_1.conda + hash: + md5: bf074df5a51c193b2d14d13c1bf404a3 + sha256: c0cdbd6ede905c2ff0c6c86277bac5f8967da373185649d47984bb4ee21f72fb + category: main + optional: false - name: scikit-learn version: 1.4.2 manager: conda @@ -7768,6 +7824,32 @@ package: sha256: f39a5620c6e8e9e98357507262a7869de2ae8cc07da8b7f84e517c9fd6c2b959 category: dev optional: true +- name: trimesh + version: 4.1.8 + manager: conda + platform: linux-64 + dependencies: + numpy: '' + python: '>=2.7' + url: https://repo.prefix.dev/conda-forge/noarch/trimesh-4.1.8-pyhd8ed1ab_0.conda + hash: + md5: 78302527eb6c9d18b07a91e6a72ef957 + sha256: 021110c37eca2f0fca85ba6ac4576c509d23079758f63942e2f9a6954282f2ce + category: main + optional: false +- name: trimesh + version: 4.1.8 + manager: conda + platform: win-64 + dependencies: + numpy: '' + python: '>=2.7' + url: https://repo.prefix.dev/conda-forge/noarch/trimesh-4.1.8-pyhd8ed1ab_0.conda + hash: + md5: 78302527eb6c9d18b07a91e6a72ef957 + sha256: 021110c37eca2f0fca85ba6ac4576c509d23079758f63942e2f9a6954282f2ce + category: main + optional: false - name: types-python-dateutil version: 2.9.0.20250516 manager: conda @@ -8558,12 +8640,12 @@ package: numpy: '>=1.26.0,<1.27.0' pydantic: '>=2.5.2,<3.0.0' scipy: '>=1.14.0,<1.15.0' - url: git+https://github.com/MiraGeoscience/geoapps-utils.git@3936709de7ce3cd7d469b6c88f34bb0a704670de + url: git+https://github.com/MiraGeoscience/geoapps-utils.git@ea323dc1e48634a19cca2b2693f989fae08880f1 hash: - sha256: 3936709de7ce3cd7d469b6c88f34bb0a704670de + sha256: ea323dc1e48634a19cca2b2693f989fae08880f1 source: type: url - url: git+https://github.com/MiraGeoscience/geoapps-utils.git@3936709de7ce3cd7d469b6c88f34bb0a704670de + url: git+https://github.com/MiraGeoscience/geoapps-utils.git@ea323dc1e48634a19cca2b2693f989fae08880f1 category: main optional: false - name: geoapps-utils @@ -8575,12 +8657,12 @@ package: numpy: '>=1.26.0,<1.27.0' pydantic: '>=2.5.2,<3.0.0' scipy: '>=1.14.0,<1.15.0' - url: git+https://github.com/MiraGeoscience/geoapps-utils.git@3936709de7ce3cd7d469b6c88f34bb0a704670de + url: git+https://github.com/MiraGeoscience/geoapps-utils.git@ea323dc1e48634a19cca2b2693f989fae08880f1 hash: - sha256: 3936709de7ce3cd7d469b6c88f34bb0a704670de + sha256: ea323dc1e48634a19cca2b2693f989fae08880f1 source: type: url - url: git+https://github.com/MiraGeoscience/geoapps-utils.git@3936709de7ce3cd7d469b6c88f34bb0a704670de + url: git+https://github.com/MiraGeoscience/geoapps-utils.git@ea323dc1e48634a19cca2b2693f989fae08880f1 category: main optional: false - name: geoh5py diff --git a/pyproject.toml b/pyproject.toml index e1a46b02..9b58e820 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,9 +63,11 @@ discretize = "0.11.*" # also in simpeg, octree-creation-app distributed = ">=2025.3, <2025.4.dev" # for dask[distributed] numpy = "~1.26.0" # also in geoh5py, simpeg pydantic = "^2.5.2" # also in geoh5py, curve-apps, geoapps-utils +Rtree = "~1.2.0" scikit-learn = "~1.4.0" scipy = "~1.14.0" tqdm = "^4.66.1" +trimesh = "~4.1.3" # solvers for simpeg: not imported, but at least one required at runtime pydiso = ">=0.1.0, <0.2.dev" diff --git a/simpeg_drivers-assets/plate_simulation_demo.geoh5 b/simpeg_drivers-assets/plate_simulation_demo.geoh5 new file mode 100644 index 00000000..86466d31 --- /dev/null +++ b/simpeg_drivers-assets/plate_simulation_demo.geoh5 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69849de2ea46ffcd817cf1209197230d3b1a3db19905bc119205e28c6f6442de +size 1343146 diff --git a/simpeg_drivers-assets/uijson/plate_simulation.ui.json b/simpeg_drivers-assets/uijson/plate_simulation.ui.json new file mode 100644 index 00000000..743d2aed --- /dev/null +++ b/simpeg_drivers-assets/uijson/plate_simulation.ui.json @@ -0,0 +1,253 @@ +{ + "version": "0.2.0-alpha.1", + "title": "Plate simulation", + "icon": "maxwellplate", + "documentation": "https://mirageoscience-plate-simulation.readthedocs-hosted.com/en/latest/", + "conda_environment": "simpeg_drivers", + "run_command": "simpeg_drivers.plate_simulation.driver", + "geoh5": "", + "monitoring_directory": "", + "simulation": { + "main": true, + "label": "TEM SimPEG Group", + "groupType": "{55ed3daf-c192-4d4b-a439-60fa987fe2b8}", + "value": "", + "enabled": true, + "tooltip": "Forward modelling SimPEG group with at least the topogarphy and survey set" + }, + "name": { + "main": true, + "label": "Label", + "value": "plate", + "enabled": true, + "tooltip": "Provide the name for the result group" + }, + "background": { + "main": true, + "group": "Basement", + "label": "Physical property (SI)", + "value": 2000.0, + "tooltip": "Value of the basement resisitivity (ohm-m), density (g/cc) or susceptibility (SI)", + "enabled": true + }, + "overburden": { + "main": true, + "group": "Overburden", + "label": "Physical property (SI)", + "value": 8000.0, + "tooltip": "Value of the overburden resisitivity (ohm-m), density (g/cc) or susceptibility (SI)", + "enabled": true + }, + "thickness": { + "main": true, + "group": "Overburden", + "label": "Thickness", + "value": 200.0, + "enabled": true, + "tooltip": "Thickness of the overburden" + }, + "number": { + "main": true, + "group": "Plate", + "label": "Number of plates", + "value": 1, + "enabled": true, + "tooltip": "If more than one the plates will be parallel, equally spaced over the center of the model" + }, + "spacing": { + "main": true, + "group": "Plate", + "label": "Spacing (m)", + "value": 0.0, + "enabled": true, + "tooltip": "Spacing between plates" + }, + "plate": { + "main": true, + "group": "Plate", + "label": "Physical property (SI)", + "value": 20.0, + "tooltip": "Value of the plate resisitivity (ohm-m), density (g/cc) or susceptibility (SI)", + "enabled": true + }, + "width": { + "label": "Thickness", + "group": "Plate", + "main": true, + "value": 90.0, + "enabled": true, + "tooltip": "Thickness of the plate" + }, + "strike_length": { + "label": "Strike length", + "group": "Plate", + "main": true, + "value": 800.0, + "enabled": true, + "tooltip": "Length of the plate along the strike direction" + }, + "dip_length": { + "label": "Dip length", + "group": "Plate", + "main": true, + "value": 800.0, + "enabled": true, + "tooltip": "Length of the plate along the dip direction" + }, + "dip": { + "label": "Dip", + "group": "Plate", + "main": true, + "value": 60.0, + "enabled": true, + "min": 0.0, + "max": 90.0, + "tooltip": "Dip of the plate in degrees from horizontal" + }, + "dip_direction": { + "label": "Dip direction", + "group": "Plate", + "main": true, + "value": 90.0, + "enabled": true, + "min": 0.0, + "max": 360.0, + "tooltip": "Direction of the dip vector in degrees from North" + }, + "relative_locations": { + "label": "Relative locations", + "main": true, + "group": "Plate", + "value": true, + "enabled": true, + "tooltip": "If checked locations are relative to the survey center and either topography or overburden in z according to 'Depth reference' selection" + }, + "easting": { + "label": "Easting (m)", + "group": "Plate", + "main": true, + "value": 0.0, + "enabled": true, + "tooltip": "If relative locations, Easting is relative to the center of the survey" + }, + "northing": { + "label": "Northing (m)", + "main": true, + "group": "Plate", + "value": 0.0, + "enabled": true, + "tooltip": "If relative locations, Northing is relative to the center of the survey" + }, + "elevation": { + "label": "Elevation (m)", + "main": true, + "group": "Plate", + "value": 0.0, + "enabled": true, + "tooltip": "If relative location, Elevation is relative to the topography or overburden according to 'Depth reference' selection" + }, + "reference_surface": { + "label": "Depth reference", + "main": true, + "group": "Plate", + "dependency": "relative_locations", + "dependencyType": "enabled", + "choiceList": [ + "topography", + "overburden" + ], + "tooltop": "If relative locations, the depth will be below the min/mean/max (according to 'Reference type') of the reference surface chosen.", + "value": "overburden" + }, + "reference_type": { + "label": "Reference type", + "main": true, + "group": "Plate", + "dependency": "relative_locations", + "dependencyType": "enabled", + "choiceList": [ + "min", + "mean", + "max" + ], + "tooltip": "If relative locations, the depth will be below the min/mean/max of the 'Depth reference' chosen", + "value": "min" + }, + "generate_sweep": { + "label": "Generate sweep file", + "main": true, + "value": false + }, + "u_cell_size": { + "min": 0.0, + "group": "Mesh", + "label": "Easting core cell size (m)", + "value": 25.0, + "enabled": true + }, + "v_cell_size": { + "min": 0.0, + "group": "Mesh", + "label": "Northing core cell size (m)", + "value": 25.0, + "enabled": true + }, + "w_cell_size": { + "min": 0.0, + "group": "Mesh", + "label": "Vertical core cell size (m)", + "value": 25.0, + "enabled": true + }, + "depth_core": { + "min": 0.0, + "group": "Mesh", + "label": "Depth of core refinement volume", + "value": 500.0, + "enabled": true + }, + "max_distance": { + "min": 0.0, + "group": "Mesh", + "label": "Maximum padding distance", + "value": 200.0, + "enabled": true + }, + "padding_distance": { + "min": 0.0, + "group": "Mesh", + "label": "Padding distance", + "value": 1000.0, + "enabled": true + }, + "diagonal_balance": { + "group": "Mesh", + "label": "Diagonal balance", + "main": false, + "value": true, + "tooltip": "Assure single octree level change on corner neighbours. UBC compatible mesh" + }, + "minimum_level": { + "enabled": true, + "group": "Mesh", + "label": "Minimum refinement level", + "main": false, + "min": 1, + "tooltip": "Minimum refinement in padding region: 2**(n-1) x base_cell", + "value": 6 + }, + "export_model": { + "main": false, + "label": "Export mesh/model", + "value": true, + "enabled": true + }, + "out_group": { + "label": "Simulation group", + "value": "", + "groupType": "{BB50AC61-A657-4926-9C82-067658E246A0}", + "visible": true, + "optional": true, + "enabled": false + } +} diff --git a/simpeg_drivers/driver.py b/simpeg_drivers/driver.py index 8ad9e250..4ded8bfa 100644 --- a/simpeg_drivers/driver.py +++ b/simpeg_drivers/driver.py @@ -688,7 +688,6 @@ def from_input_file(cls, ifile: InputFile) -> InversionDriver: class InversionLogger: def __init__(self, logfile, driver): self.driver = driver - self.forward = driver.params.forward_only self.terminal = sys.stdout self.log = open(self.get_path(logfile), "w", encoding="utf8") self.initial_time = time() diff --git a/simpeg_drivers/plate_simulation/__init__.py b/simpeg_drivers/plate_simulation/__init__.py new file mode 100644 index 00000000..4d06f672 --- /dev/null +++ b/simpeg_drivers/plate_simulation/__init__.py @@ -0,0 +1,9 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2025 Mira Geoscience Ltd. ' +# ' +# This file is part of simpeg-drivers package. ' +# ' +# simpeg-drivers is distributed under the terms and conditions of the MIT License ' +# (see LICENSE file at the root of this source code package). ' +# ' +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' diff --git a/simpeg_drivers/plate_simulation/driver.py b/simpeg_drivers/plate_simulation/driver.py new file mode 100644 index 00000000..0956e31c --- /dev/null +++ b/simpeg_drivers/plate_simulation/driver.py @@ -0,0 +1,338 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2025 Mira Geoscience Ltd. ' +# ' +# This file is part of simpeg-drivers package. ' +# ' +# simpeg-drivers is distributed under the terms and conditions of the MIT License ' +# (see LICENSE file at the root of this source code package). ' +# ' +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +from __future__ import annotations + +import sys +from pathlib import Path + +import numpy as np +from geoapps_utils.base import Driver, get_logger +from geoapps_utils.utils.transformations import azimuth_to_unit_vector +from geoh5py.data import FloatData, ReferencedData +from geoh5py.groups import UIJsonGroup +from geoh5py.objects import Octree, Points, Surface +from geoh5py.shared.utils import fetch_active_workspace +from geoh5py.ui_json import InputFile, monitored_directory_copy +from octree_creation_app.driver import OctreeDriver +from param_sweeps.generate import generate + +from simpeg_drivers.driver import InversionDriver, InversionLogger +from simpeg_drivers.options import BaseForwardOptions +from simpeg_drivers.plate_simulation.models.events import Anomaly, Erosion, Overburden +from simpeg_drivers.plate_simulation.models.parametric import Plate +from simpeg_drivers.plate_simulation.models.series import DikeSwarm, Geology +from simpeg_drivers.plate_simulation.options import PlateSimulationOptions + + +logger = get_logger(__name__) + + +class PlateSimulationDriver(Driver): + """ + Driver for simulating background + plate + overburden model. + + :param params: Parameters for plate simulation (mesh, model and + series). + :param plate: Plate object used to add anomaly to the model. + :param mesh: Octree mesh in which model is built for the simulation. + :param model: Model to simulate. + :param survey: Survey object for the simulation + """ + + _params_class = PlateSimulationOptions + + def __init__(self, params: PlateSimulationOptions): + super().__init__(params) + + self._plates: list[Plate] | None = None + self._survey: Points | None = None + self._mesh: Octree | None = None + self._model: FloatData | None = None + self._simulation_parameters: BaseForwardOptions | None = None + self._simulation_driver: InversionDriver | None = None + self._out_group = self.validate_out_group(self.params.out_group) + + def run(self) -> InversionDriver: + """Create octree mesh, fill model, and simulate.""" + + logger.info("running the simulation...") + with fetch_active_workspace(self.params.geoh5, mode="r+"): + self.simulation_driver.run() + self.out_group.add_ui_json() + if ( + self.params.monitoring_directory is not None + and Path(self.params.monitoring_directory).is_dir() + ): + monitored_directory_copy( + str(Path(self.params.monitoring_directory).resolve()), + self.out_group, + ) + + logger.info("done.") + logger.handlers.clear() + + return self.simulation_driver + + @property + def out_group(self) -> UIJsonGroup: + """ + Returns the output group for the simulation. + """ + return self._out_group + + def validate_out_group(self, out_group: UIJsonGroup | None) -> UIJsonGroup: + """ + Validate or create a UIJsonGroup to store results. + + :param value: Output group from selection. + """ + if isinstance(out_group, UIJsonGroup): + return out_group + + with fetch_active_workspace(self.params.geoh5, mode="r+"): + out_group = UIJsonGroup.create( + self.params.geoh5, + name="Plate Simulation", + ) + out_group.entity_type.name = "Plate Simulation" + self.params = self.params.model_copy(update={"out_group": out_group}) + out_group.options = InputFile.stringify( + InputFile.demote(self.params.input_file.ui_json) + ) + out_group.metadata = None + + return out_group + + @property + def simulation_driver(self) -> InversionDriver: + if self._simulation_driver is None: + with fetch_active_workspace(self.params.geoh5, mode="r+"): + self.simulation_parameters.mesh = self.mesh + self.simulation_parameters.models.starting_model = self.model + + if not isinstance( + self.simulation_parameters.active_cells.topography_object, + Surface | Points, + ): + raise ValueError( + "The topography object of the forward simulation must be a 'Surface'." + ) + + self.simulation_parameters.out_group = None + driver_class = InversionDriver.driver_class_from_name( + self.simulation_parameters.inversion_type, forward_only=True + ) + self._simulation_driver = driver_class(self.simulation_parameters) + self._simulation_driver.out_group.parent = self.out_group + + return self._simulation_driver + + @property + def simulation_parameters(self) -> BaseForwardOptions: + if self._simulation_parameters is None: + self._simulation_parameters = self.params.simulation_parameters() + if self._simulation_parameters.physical_property == "conductivity": + self._simulation_parameters.models.model_type = "Resistivity (Ohm-m)" + return self._simulation_parameters + + @property + def survey(self): + if self._survey is None: + self._survey = self.simulation_parameters.data_object + + return self._survey + + @property + def plates(self) -> list[Plate]: + """Generate sequence of plates.""" + if self._plates is None: + offset = ( + self.params.model.overburden_model.thickness + if self.params.model.plate_model.reference_surface == "overburden" + else 0.0 + ) + center = self.params.model.plate_model.center( + self.survey, + self.topography, + depth_offset=-1 * offset, + ) + plate = Plate( + self.params.model.plate_model, + center, + ) + self._plates = self.replicate( + plate, + self.params.model.plate_model.number, + self.params.model.plate_model.spacing, + self.params.model.plate_model.dip_direction, + ) + return self._plates + + @property + def topography(self) -> Surface | Points: + return self.simulation_parameters.active_cells.topography_object + + @property + def mesh(self) -> Octree: + """Returns an octree mesh built from mesh parameters.""" + if self._mesh is None: + self._mesh = self.make_mesh() + + return self._mesh + + @property + def model(self) -> FloatData: + """Returns the model built from model parameters.""" + if self._model is None: + self._model = self.make_model() + + return self._model + + def make_mesh(self) -> Octree: + """ + Build specialized mesh for plate simulation from parameters. + + Mesh contains refinements for topography and any plates. + """ + + logger.info("making the mesh...") + octree_params = self.params.mesh.octree_params( + self.survey, + self.simulation_parameters.active_cells.topography_object, + [p.surface.copy(parent=self.out_group) for p in self.plates], + ) + octree_driver = OctreeDriver(octree_params) + mesh = octree_driver.run() + mesh.parent = self.out_group + + return mesh + + def make_model(self) -> FloatData: + """Create background + plate and overburden model from parameters.""" + + logger.info("Building the model...") + + overburden = Overburden( + topography=self.simulation_parameters.active_cells.topography_object, + thickness=self.params.model.overburden_model.thickness, + value=self.params.model.overburden_model.overburden, + ) + + dikes = DikeSwarm( + [Anomaly(plate, plate.params.plate) for plate in self.plates], + name="plates", + ) + + erosion = Erosion( + surface=self.simulation_parameters.active_cells.topography_object, + ) + + scenario = Geology( + workspace=self.params.geoh5, + mesh=self.mesh, + background=self.params.model.background, + history=[dikes, overburden, erosion], + ) + + geology, event_map = scenario.build() + value_map = {k: v[0] for k, v in event_map.items()} + physical_property_map = {k: v[1] for k, v in event_map.items()} + + physical_property = self.simulation_parameters.physical_property + if physical_property == "conductivity": + physical_property = "resistivity" + + model = self.mesh.add_data( + { + "geology": { + "type": "referenced", + "values": geology, + "value_map": value_map, + } + } + ) + if isinstance(model, ReferencedData): + model.add_data_map(physical_property, physical_property_map) + + starting_model_values = geology.copy() + for k, v in physical_property_map.items(): + starting_model_values[geology == k] = v + + starting_model = self.mesh.add_data( + {"starting_model": {"values": starting_model_values}} + ) + + if not isinstance(starting_model, FloatData): + raise ValueError("Starting model could not be created.") + + return starting_model + + @staticmethod + def replicate( + plate: Plate, + number: int, + spacing: float, + azimuth: float, + ) -> list[Plate]: + """ + Replicate a plate n times along an azimuth centered at origin. + + Plate names will be indexed. + + :param plate: models.parametric.Plate to be replicated. + :param number: Number of plates returned. + :param spacing: Spacing between plates. + :param azimuth: Azimuth of the axis along with plates are replicated. + """ + offsets = (np.arange(number) * spacing) - ((number - 1) * spacing / 2) + + plates = [] + for i in range(number): + center = np.r_[plate.center] + azimuth_to_unit_vector(azimuth) * offsets[i] + new = Plate(plate.params.model_copy(), center) + new.params.name = f"{plate.params.name} offset {i + 1}" + plates.append(new) + return plates + + @staticmethod + def start(ifile: str | Path | InputFile): + """Run the plate simulation driver from an input file.""" + + if isinstance(ifile, str): + ifile = Path(ifile) + + if isinstance(ifile, Path): + ifile = InputFile.read_ui_json(ifile) + + if ifile.data is None: # type: ignore + raise ValueError("Input file has no data loaded.") + + generate_sweep = ifile.data["generate_sweep"] # type: ignore + if generate_sweep: + filepath = Path(ifile.path_name) # type: ignore + ifile.data["generate_sweep"] = False # type: ignore + name = filepath.name + path = filepath.parent + ifile.write_ui_json(name=name, path=path) # type: ignore + generate( # pylint: disable=unexpected-keyword-arg + str(filepath), update_values={"conda_environment": "plate_simulation"} + ) + return None + + with ifile.geoh5.open(mode="r+"): # type: ignore + params = PlateSimulationOptions.build(ifile) + + return PlateSimulationDriver(params).run() + + +if __name__ == "__main__": + file = Path(sys.argv[1]) + PlateSimulationDriver.start(file) diff --git a/simpeg_drivers/plate_simulation/models/__init__.py b/simpeg_drivers/plate_simulation/models/__init__.py new file mode 100644 index 00000000..ca2bdfa3 --- /dev/null +++ b/simpeg_drivers/plate_simulation/models/__init__.py @@ -0,0 +1,11 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2025 Mira Geoscience Ltd. ' +# ' +# This file is part of simpeg-drivers package. ' +# ' +# simpeg-drivers is distributed under the terms and conditions of the MIT License ' +# (see LICENSE file at the root of this source code package). ' +# ' +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +EventMap = dict[int, tuple[str, float]] diff --git a/simpeg_drivers/plate_simulation/models/events.py b/simpeg_drivers/plate_simulation/models/events.py new file mode 100644 index 00000000..3affe1a1 --- /dev/null +++ b/simpeg_drivers/plate_simulation/models/events.py @@ -0,0 +1,185 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2025 Mira Geoscience Ltd. ' +# ' +# This file is part of simpeg-drivers package. ' +# ' +# simpeg-drivers is distributed under the terms and conditions of the MIT License ' +# (see LICENSE file at the root of this source code package). ' +# ' +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +from abc import ABC, abstractmethod + +import numpy as np +from geoh5py.objects import Octree, Surface +from geoh5py.shared.utils import find_unique_name + +from simpeg_drivers.plate_simulation.models import EventMap +from simpeg_drivers.plate_simulation.models.parametric import Boundary, Parametric + + +class Event(ABC): + """ + Parameterized geological events that modify the model. + + :param value: Physical property value assigned to the event. + :param name: Name of the event. + """ + + def __init__(self, value: float, name: str): + self.value = value + self.name = name + + def _update_event_map(self, event_map: EventMap) -> tuple[int, EventMap]: + """ + Increase the event id and add name and physical property to the event map. + + :param event_map: mapping event ids to names and physical properties. + + :return: Updated event id. + :return: Updated event map. + """ + + event_id = max(event_map) + 1 + names = [elem[0] for elem in event_map.values()] + name = find_unique_name(self.name, names) + event_map[event_id] = (name, self.value) + + return event_id, event_map + + @abstractmethod + def realize( + self, mesh: Octree, model: np.ndarray, event_map: EventMap + ) -> tuple[np.ndarray, EventMap]: + """ + Update the model with the event realization + + :param mesh: Octree mesh on which the model is defined. + :param model: Model to be updated by the event. + :param event_map: mapping event ids to names and physical properties. + + :return: Updated model and list of events including itself. + """ + + +class Deposition(Event): + """ + Fills model below a surface with a provided property value. + + :param surface: Surface representing the top of a sedimentary layer. + :param value: The value given to the model below the surface. + :param name: Name of the event. + """ + + def __init__(self, surface: Surface, value: float, name: str = "Deposition"): + self.surface = Boundary(surface) + super().__init__(value, name) + + def realize( + self, mesh: Octree, model: np.ndarray, event_map: EventMap + ) -> tuple[np.ndarray, EventMap]: + """ + Implementation of parent Event abstract method. + Fill the model below the surface with the layer's value. + """ + + event_id, event_map = self._update_event_map(event_map) + model[self.surface.mask(mesh)] = event_id + + return model, event_map + + +class Overburden(Event): + """ + Add an overburden layer below the topography surface. + + :param topography: Surface representing the topography. + :param thickness: Thickness of the overburden layer. + :param value: Model value given to the overburden layer. + :param name: Name of the event. + """ + + def __init__( + self, + topography: Surface, + thickness: float, + value: float, + name: str = "Overburden", + ): + self.topography = Boundary(topography) + self.thickness = thickness + super().__init__(value, name) + + def realize( + self, mesh: Octree, model: np.ndarray, event_map: EventMap + ) -> tuple[np.ndarray, EventMap]: + """ + Implementation of parent Event abstract method. + Fill the model below the topography with the overburden value. + """ + event_id, event_map = self._update_event_map(event_map) + model[ + ~self.topography.mask(mesh, offset=-1 * self.thickness, reference="center") + ] = event_id + + return model, event_map + + +class Erosion(Event): + """ + Erode the model at a provided surface. + + :param surface: The surface above which the model will be + eroded (filled with nan values). + :param value: The value given to the eroded model, default to nan. + :param name: Name of the Erosion event. + """ + + def __init__(self, surface: Surface, value: float = np.nan, name: str = "Erosion"): + self.surface = Boundary(surface) + super().__init__(value, name) + + def realize( + self, mesh: Octree, model: np.ndarray, event_map: EventMap + ) -> tuple[np.ndarray, EventMap]: + """ + Implementation of parent Event abstract method. + Fill the model above the surface with nan values. + """ + + event_id, event_map = self._update_event_map(event_map) + model[~self.surface.mask(mesh)] = event_id + + return model, event_map + + +class Anomaly(Event): + """ + Enrich or deplete the model within a close body. + + :param body: Closed body within which the model will be filled + with the anomaly value. + :param value: Model value assigned to the anomaly. + :param name: Name of the event. + """ + + def __init__(self, body: Parametric, value: float, name: str = "Anomaly"): + self.body = body + super().__init__(value, name) + + def realize( + self, mesh: Octree, model: np.ndarray, event_map: EventMap, coeval: bool = False + ) -> tuple[np.ndarray, EventMap]: + """ + Implementation of parent Event abstract method. + Fill the model within the surface with the anomaly value. + """ + + if coeval: + event_id = max(event_map) + else: + event_id, event_map = self._update_event_map(event_map) + + model[self.body.mask(mesh)] = event_id + + return model, event_map diff --git a/simpeg_drivers/plate_simulation/models/options.py b/simpeg_drivers/plate_simulation/models/options.py new file mode 100644 index 00000000..c2f77828 --- /dev/null +++ b/simpeg_drivers/plate_simulation/models/options.py @@ -0,0 +1,157 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2025 Mira Geoscience Ltd. ' +# ' +# This file is part of simpeg-drivers package. ' +# ' +# simpeg-drivers is distributed under the terms and conditions of the MIT License ' +# (see LICENSE file at the root of this source code package). ' +# ' +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +from typing import TypeVar + +import numpy as np +from geoh5py.objects import Points +from pydantic import ( + BaseModel, + ConfigDict, + ValidationInfo, + field_validator, + model_validator, +) + + +T = TypeVar("T") + + +class PlateOptions(BaseModel): + """ + Parameters describing an anomalous plate. + + :param plate: Value given to the plate(s). + :param width: V-size of the plate. + :param strike_length: U-size of the plate. + :param dip_length: W-size of the plate. + :param dip: Orientation of the v-axis in degree from horizontal. + :param dip_direction: Orientation of the u axis in degree from north. + :param reference: Point of rotation to be 'center' or 'top'. + :param number: Number of offset plates to be created. + :param spacing: Spacing between plates. + :param relative_locations: If True locations are relative to survey in xy and + mean topography in z. + :param easting: Easting offset relative to survey. + :param northing: Northing offset relative to survey. + :param elevation: plate(s) elevation. May be true elevation or relative to + overburden or topography. + :param reference_surface: Switches between using topography and overburden as + elevation reference of the plate. + :param reference_type: Type of reference for plate elevation. Can be 'mean' + 'min', or 'max'. Resulting elevation will be relative to the mean, + minimum, or maximum of the reference surface. + """ + + model_config = ConfigDict(arbitrary_types_allowed=True) + + name: str = "Plate" + plate: float + width: float + strike_length: float + dip_length: float + dip: float = 90.0 + dip_direction: float = 90.0 + number: int = 1 + spacing: float = 0.0 + relative_locations: bool = False + easting: float = 0.0 + northing: float = 0.0 + elevation: float + reference_surface: str = "topography" + reference_type: str = "mean" + + @field_validator("reference_surface", "reference_type", mode="before") + @classmethod + def none_to_default(cls, value: T | None, info: ValidationInfo) -> T: + return value or cls.model_fields[info.field_name].default # pylint: disable=unsubscriptable-object + + @model_validator(mode="after") + def single_plate(self): + if self.number == 1: + self.spacing = 0.0 + return self + + @property + def halfplate(self): + """Compute half the z-projection length of the plate.""" + return 0.5 * self.dip_length * np.sin(np.deg2rad(self.dip)) + + def center( + self, + survey: Points, + surface: Points, + depth_offset: float = 0.0, + ) -> tuple[float, float, float]: + """ + Find the plate center relative to a survey and topography. + + :param survey: geoh5py survey object for plate simulation. + :param surface: Points-like object to reference plate depth from. + :param depth_offset: Additional offset to be added to the depth of the plate. + """ + return *self._get_xy(survey), self._get_z(surface, depth_offset) + + def _get_xy(self, survey: Points) -> tuple[float, float]: + """Return true or relative locations in x and y.""" + + if self.relative_locations: + return ( + survey.vertices[:, 0].mean() + self.easting, + survey.vertices[:, 1].mean() + self.northing, + ) + + return self.easting, self.northing + + def _get_z(self, surface: Points, offset: float = 0.0) -> float: + """ + Return true or relative locations in z. + + :param surface: Points-like object to reference plate depth from. + :offset: Additional offset to be added to the depth. + + """ + if surface.vertices is None: + raise ValueError("Topography object has no vertices.") + if self.relative_locations: + z = getattr(surface.vertices[:, 2], self.reference_type)() + z += offset + self.elevation - self.halfplate + else: + z = self.elevation + + return z + + +class OverburdenOptions(BaseModel): + """ + Parameters for the overburden layer. + + :param thickness: Thickness of the overburden layer. + :param overburden: Value given to the overburden layer. + """ + + thickness: float + overburden: float + + +class ModelOptions(BaseModel): + """ + Parameters for the blackground + overburden and plate model. + + :param background: Value given to the background. + :param overburden: Overburden layer parameters. + :param plate: Plate parameters. + """ + + model_config = ConfigDict(arbitrary_types_allowed=True) + + background: float + overburden_model: OverburdenOptions + plate_model: PlateOptions diff --git a/simpeg_drivers/plate_simulation/models/parametric.py b/simpeg_drivers/plate_simulation/models/parametric.py new file mode 100644 index 00000000..185882db --- /dev/null +++ b/simpeg_drivers/plate_simulation/models/parametric.py @@ -0,0 +1,247 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2025 Mira Geoscience Ltd. ' +# ' +# This file is part of simpeg-drivers package. ' +# ' +# simpeg-drivers is distributed under the terms and conditions of the MIT License ' +# (see LICENSE file at the root of this source code package). ' +# ' +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +from __future__ import annotations + +from abc import ABC, abstractmethod + +import numpy as np +from geoapps_utils.modelling.plates import PlateModel, inside_plate +from geoapps_utils.utils.transformations import ( + rotate_points, + rotate_xyz, + x_rotation_matrix, + z_rotation_matrix, +) +from geoh5py.objects import Octree, Surface +from geoh5py.shared.utils import fetch_active_workspace +from geoh5py.workspace import Workspace +from trimesh import Trimesh +from trimesh.proximity import ProximityQuery + +from simpeg_drivers.plate_simulation.models.options import PlateOptions +from simpeg_drivers.utils.utils import active_from_xyz + + +class Parametric(ABC): + """ + Base class representing parametric geometries. + """ + + def __init__(self, surface: Surface): + if not isinstance(surface, Surface): + raise TypeError( + "Input attribute 'surface' should be in instance of geoh5py.Surface." + ) + + self._surface = surface + + @property + def surface(self): + """ + Surface object representing the shape of the object. + """ + return self._surface + + @abstractmethod + def mask(self, mesh: Octree) -> np.ndarray: + """ + Return logical for cells inside the parametric object. + """ + + +class Plate(Parametric): + """ + Define a rotated rectangular block in 3D space + + :param params: Parameters describing the plate. + :param surface: Surface object representing the plate. + """ + + def __init__( + self, + params: PlateOptions, + center: tuple[float, float, float] = ( + 0.0, + 0.0, + 0.0, + ), + workspace: Workspace | None = None, + ): + self.params = params + self.center = center + self._workspace = workspace + super().__init__(self._create_surface()) + + def _create_surface(self) -> Surface: + """ + Create a surface object from a plate object. + + :param workspace: Workspace object to create the surface in. + :param out_group: Output group to store the surface. + """ + with fetch_active_workspace(self.workspace) as ws: + surface = Surface.create( + ws, + vertices=self.vertices, + cells=self.triangles, + name=self.params.name, + ) + + return surface + + @property + def surface(self): + """ + A surface object representing the plate. + """ + return self._surface + + @property + def triangles(self) -> np.ndarray: + """Triangulation of the block.""" + return np.vstack( + [ + [0, 2, 1], + [1, 2, 3], + [0, 1, 4], + [4, 1, 5], + [1, 3, 5], + [5, 3, 7], + [2, 6, 3], + [3, 6, 7], + [0, 4, 2], + [2, 4, 6], + [4, 5, 6], + [6, 5, 7], + ] + ) + + @property + def vertices(self) -> np.ndarray: + """Vertices for triangulation of a rectangular prism in 3D space.""" + + u_1 = self.center[0] - (self.params.strike_length / 2.0) + u_2 = self.center[0] + (self.params.strike_length / 2.0) + v_1 = self.center[1] - (self.params.dip_length / 2.0) + v_2 = self.center[1] + (self.params.dip_length / 2.0) + w_1 = self.center[2] - (self.params.width / 2.0) + w_2 = self.center[2] + (self.params.width / 2.0) + + vertices = np.array( + [ + [u_1, v_1, w_1], + [u_2, v_1, w_1], + [u_1, v_2, w_1], + [u_2, v_2, w_1], + [u_1, v_1, w_2], + [u_2, v_1, w_2], + [u_1, v_2, w_2], + [u_2, v_2, w_2], + ] + ) + + return self._rotate(vertices) + + @property + def workspace(self) -> Workspace: + if self._workspace is None: + self._workspace = Workspace() + + return self._workspace + + def _rotate(self, vertices: np.ndarray) -> np.ndarray: + """Rotate vertices and adjust for reference point.""" + theta = -1 * self.params.dip_direction + phi = -1 * self.params.dip + rotated_vertices = rotate_xyz(vertices, self.center, theta, phi) + + return rotated_vertices + + def mask(self, mesh: Octree) -> np.ndarray: + plate = PlateModel( + strike_length=self.params.strike_length, + dip_length=self.params.dip_length, + width=self.params.width, + direction=self.params.dip_direction, + dip=self.params.dip, + origin=self.center, + ) + rotations = [ + z_rotation_matrix(np.deg2rad(self.params.dip_direction)), + x_rotation_matrix(np.deg2rad(self.params.dip)), + ] + rotated_centers = rotate_points( + mesh.centroids, origin=plate.origin, rotations=rotations + ) + return inside_plate(rotated_centers, plate) + + +class Body(Parametric): + """ + Represents a closed surface in the model. + + :param surface: geoh5py Surface object representing a closed surface + """ + + def mask(self, mesh: Octree) -> np.ndarray: + """ + True for cells that lie within the closed surface. + + :param mesh: Octree mesh on which the mask is computed. + """ + triangulation = Trimesh( + vertices=self.surface.vertices, faces=self.surface.cells + ) + proximity_query = ProximityQuery(triangulation) + dist = proximity_query.signed_distance(mesh.centroids) + return dist > 0 + + +class Boundary(Parametric): + """ + Represents a boundary in a model. + + :param surface: geoh5py Surface object representing a boundary + in the model. + """ + + def vertical_shift(self, offset: float) -> np.ndarray: + """ + Returns the surface vertices shifted vertically by offset. + + :param offset: Shifts vertices in up (positive) or down (negative). + """ + + if self.surface.vertices is None: + raise ValueError("Surface vertices are not defined.") + + shift = np.c_[ + np.zeros(self.surface.vertices.shape[0]), + np.zeros(self.surface.vertices.shape[0]), + np.ones(self.surface.vertices.shape[0]) * offset, + ] + return self.surface.vertices + shift + + def mask( + self, mesh: Octree, offset: float = 0.0, reference: str = "center" + ) -> np.ndarray: + """ + True for cells whose reference lie below the surface. + + :param mesh: Octree mesh on which the mask is computed. + :param offset: Statically shift the surface on which the mask + is computed. + :param reference: Use "bottom", "center" or "top" of the cells + in determining the mask. + + """ + + return active_from_xyz(mesh, self.vertical_shift(offset), reference) diff --git a/simpeg_drivers/plate_simulation/models/series.py b/simpeg_drivers/plate_simulation/models/series.py new file mode 100644 index 00000000..2f67a9a0 --- /dev/null +++ b/simpeg_drivers/plate_simulation/models/series.py @@ -0,0 +1,235 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2025 Mira Geoscience Ltd. ' +# ' +# This file is part of simpeg-drivers package. ' +# ' +# simpeg-drivers is distributed under the terms and conditions of the MIT License ' +# (see LICENSE file at the root of this source code package). ' +# ' +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +from __future__ import annotations + +from abc import ABC, abstractmethod +from collections.abc import Sequence +from typing import TYPE_CHECKING + +import numpy as np +from geoh5py import Workspace +from geoh5py.objects import Octree +from geoh5py.shared.utils import fetch_active_workspace + +from simpeg_drivers.plate_simulation.models import EventMap +from simpeg_drivers.plate_simulation.models.events import ( + Anomaly, + Erosion, + Event, + Overburden, +) + + +if TYPE_CHECKING: + from .events import Deposition + + +class Series(ABC): + """ + Sequence of geological events. + + :param history: Sequence of geological events. + """ + + def __init__(self, history: Sequence[Event | Series]): + self.history = history + + def realize( + self, mesh: Octree, model: np.ndarray, event_map: EventMap + ) -> tuple[np.ndarray, EventMap]: + """ + Realize each event in the history. + + :param mesh: Octree mesh on which the model is defined. + :param model: Model to be updated by the events in the history. + :param event_map: mapping event ids to names and physical properties. + """ + + for event in self.history: + model, event_map = event.realize(mesh, model, event_map) + + return model, event_map + + @property + @abstractmethod + def history(self): + """Sequence of geological events.""" + + @history.setter + @abstractmethod + def history(self, events): + pass + + +class Lithology(Series): + """ + Model a sequence of sedimentary layers. + + :param history: Sequence of layers to be deposited. These should be + ordered so that the first layer in the list is the bottom unit + and the last layer is the top unit + """ + + # TODO: Provide an optional bottom surface to begin the deposition. + + def __init__(self, history: Sequence[Deposition]): + super().__init__(history[::-1]) + + @property + def history(self) -> Sequence[Deposition | Erosion]: + """Sequence of geological events.""" + return self._history + + @history.setter + def history(self, events): + if not all(isinstance(k, Event) for k in events): + raise ValueError("History must be a sequence of geological events.") + + self._history = events + + +class DikeSwarm(Series): + """ + Model a set of dike intrusions. + + :param history: Sequence of intrusions represented by Anomaly objects. + :param name: Name of the dike swarm. + """ + + def __init__(self, history: Sequence[Anomaly], name: str = "Dike Swarm"): + super().__init__(history) + self.name = name + + def realize( + self, mesh: Octree, model: np.ndarray, event_map: EventMap + ) -> tuple[np.ndarray, EventMap]: + """ + Realize each event in the history. + + :param mesh: Octree mesh on which the model is defined. + :param model: Model to be updated by the events in the history. + :param event_map: mapping event ids to names and physical properties. + """ + + event_id = max(event_map) + 1 + event_map[event_id] = (self.name, self.history[0].value) + for event in self.history: + model, event_map = event.realize(mesh, model, event_map, coeval=True) + + return model, event_map + + @property + def history(self) -> Sequence[Anomaly]: + """Sequence of geological events.""" + return self._history + + @history.setter + def history(self, events): + if not all(isinstance(k, Anomaly) for k in events): + raise ValueError("History must be a sequence of geological Anomaly.") + + self._history = events + + +class GeologyViolationError(Exception): + """Raise when a geological history is invalid.""" + + def __init__(self, message): + super().__init__(message) + + +class Geology(Series): + """ + Ensures that a history is valid. + + :param history: Sequence of geological events to be validated. + """ + + def __init__( + self, + workspace: Workspace, + *, + mesh: Octree, + background: float, + history: Sequence[Event | Series], + ): + super().__init__(history) + self.workspace = workspace + self.mesh = mesh + self.background = background + + def __iter__(self): + return iter(self.history) + + @property + def history(self) -> Sequence[Event | Series]: + """Sequence of geological events.""" + return self._history + + @history.setter + def history(self, events): + if not all(isinstance(k, Event | Series) for k in events): + raise ValueError( + "History must be a sequence of geological Event or Series." + ) + + self._validate_history(events) + self._history = events + + @property + def mesh(self) -> Octree: + """Octree mesh on which the model is defined.""" + return self._mesh + + @mesh.setter + def mesh(self, val: Octree): + if val.n_cells is None: + raise ValueError("Mesh must have n_cells.") + self._mesh = val + + def build(self) -> tuple[np.ndarray, EventMap]: + """ + Realize the geological events in the scenario. + + :return: Model and event map. + """ + with fetch_active_workspace(self.workspace, mode="r+"): + if self.mesh.n_cells is None: + raise ValueError("Mesh must have n_cells.") + event_map = {1: ("Background", self.background)} + geology, event_map = super().realize( + self.mesh, np.ones(self.mesh.n_cells), event_map + ) + + return geology, event_map + + def _validate_history(self, events: Sequence[Event | Series]): + """Throw exception if the history isn't valid.""" + self._validate_overburden(events) + self._validate_topography(events) + + def _validate_overburden(self, events: Sequence[Event | Series]): + """Throw exception if Overburden is not the second last event in the history.""" + + if any(isinstance(k, Overburden) for k in events) and not isinstance( + events[-2], Overburden + ): + raise GeologyViolationError( + "Overburden events must occur before the final erosion in the history." + ) + + def _validate_topography(self, events: Sequence[Event | Series]): + """Throw exception if the last event isn't an erosion.""" + + if not isinstance(events[-1], Erosion): + raise GeologyViolationError( + "The last event in a geological history must be an erosion." + ) diff --git a/simpeg_drivers/plate_simulation/options.py b/simpeg_drivers/plate_simulation/options.py new file mode 100644 index 00000000..bf5e35c5 --- /dev/null +++ b/simpeg_drivers/plate_simulation/options.py @@ -0,0 +1,163 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2025 Mira Geoscience Ltd. ' +# ' +# This file is part of simpeg-drivers package. ' +# ' +# simpeg-drivers is distributed under the terms and conditions of the MIT License ' +# (see LICENSE file at the root of this source code package). ' +# ' +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +from copy import deepcopy +from pathlib import Path +from typing import ClassVar + +from geoapps_utils.driver.data import BaseData +from geoh5py.groups import SimPEGGroup, UIJsonGroup +from geoh5py.objects import ObjectBase, Points, Surface +from geoh5py.ui_json import InputFile +from octree_creation_app.params import OctreeParams +from pydantic import BaseModel + +from simpeg_drivers import assets_path +from simpeg_drivers.electricals.direct_current.three_dimensions.options import ( + DC3DForwardOptions, +) +from simpeg_drivers.electromagnetics.frequency_domain.options import ( + FDEMForwardOptions, +) +from simpeg_drivers.electromagnetics.time_domain.options import ( + TDEMForwardOptions, +) +from simpeg_drivers.natural_sources.magnetotellurics.options import ( + MTForwardOptions, +) +from simpeg_drivers.natural_sources.tipper.options import TipperForwardOptions +from simpeg_drivers.options import BaseForwardOptions +from simpeg_drivers.potential_fields.gravity.options import GravityForwardOptions +from simpeg_drivers.potential_fields.magnetic_vector.options import ( + MVIForwardOptions, +) + +from .models.options import ModelOptions + + +PARAM_MAP = { + "gravity": GravityForwardOptions, + "tdem": TDEMForwardOptions, + "fem": FDEMForwardOptions, + "magnetotellurics": MTForwardOptions, + "direct current 3d": DC3DForwardOptions, + "magnetic vector": MVIForwardOptions, + "tipper": TipperForwardOptions, +} + + +class MeshOptions(BaseModel): + """Core parameters for octree mesh creation.""" + + u_cell_size: float + v_cell_size: float + w_cell_size: float + padding_distance: float + depth_core: float + max_distance: float + minimum_level: int = 8 + diagonal_balance: bool = False + + def octree_params( + self, survey: ObjectBase, topography: Surface | Points, plates: list[Surface] + ): + refinements = [ + { + "refinement_object": survey, + "levels": [4, 4, 4], + "horizon": False, + }, + { + "refinement_object": topography, + "levels": [0, 2], + "horizon": True, + "distance": 1000.0, + }, + ] + for plate in plates: + refinements.append( + { + "refinement_object": plate, + "levels": [2, 1], + "horizon": False, + } + ) + + octree_params = OctreeParams( + geoh5=survey.workspace, + objects=survey, + u_cell_size=self.u_cell_size, + v_cell_size=self.v_cell_size, + w_cell_size=self.w_cell_size, + horizontal_padding=self.padding_distance, + vertical_padding=self.padding_distance, + depth_core=self.depth_core, + max_distance=self.max_distance, + minimum_level=self.minimum_level, + diagonal_balance=self.diagonal_balance, + refinements=refinements, + ) + + assert isinstance(survey.workspace.h5file, Path) + path = survey.workspace.h5file.parent + octree_params.write_ui_json(path / "octree.ui.json") + return octree_params + + +class PlateSimulationOptions(BaseData): + """ + Parameters for the plate simulation driver. + + geoh5: Workspace in which the model will be built and results stored. + mesh: Parameters for the octree mesh. + model: Parameters for the background + overburden and plate model. + simulation: Simpeg group containing simulation options and a survey. Any + mesh or starting model selections will be replaced by the objects + created by the driver. + """ + + name: ClassVar[str] = "plate_simulation" + default_ui_json: ClassVar[Path] = assets_path() / "uijson/plate_simulation.ui.json" + title: ClassVar[str] = "Plate Simulation" + run_command: ClassVar[str] = "simpeg_drivers.plate_simulation.driver" + out_group: UIJsonGroup | None = None + + mesh: MeshOptions + model: ModelOptions + simulation: SimPEGGroup + + def simulation_parameters(self) -> BaseForwardOptions: + """ + Create SimPEG parameters from the simulation options. + + A new SimPEGGroup is created inside the out_group to store the + result of the forward simulation. + """ + simulation_options = deepcopy(self.simulation.options) + simulation_options["geoh5"] = self.geoh5 + simulation_options["forward_only"] = ( + True # TODO remove this when mechanics use ForwardOptions + ) + + input_file = InputFile(ui_json=simulation_options, validate=False) + if input_file.ui_json is None: + raise ValueError("Input file must have ui_json set.") + + input_file.ui_json["mesh"]["value"] = None + + if input_file.data is None: + raise ValueError("Input file data must be set.") + + if input_file.data["inversion_type"] in PARAM_MAP: + return PARAM_MAP[input_file.data["inversion_type"]].build(input_file.data) + + raise NotImplementedError( + f"Unknown inversion type: {input_file.data['inversion_type']}" + ) diff --git a/tests/plate_simulation/__init__.py b/tests/plate_simulation/__init__.py new file mode 100644 index 00000000..4d06f672 --- /dev/null +++ b/tests/plate_simulation/__init__.py @@ -0,0 +1,9 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2025 Mira Geoscience Ltd. ' +# ' +# This file is part of simpeg-drivers package. ' +# ' +# simpeg-drivers is distributed under the terms and conditions of the MIT License ' +# (see LICENSE file at the root of this source code package). ' +# ' +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' diff --git a/tests/plate_simulation/models/__init__.py b/tests/plate_simulation/models/__init__.py new file mode 100644 index 00000000..87638e04 --- /dev/null +++ b/tests/plate_simulation/models/__init__.py @@ -0,0 +1,53 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2025 Mira Geoscience Ltd. ' +# ' +# This file is part of simpeg-drivers package. ' +# ' +# simpeg-drivers is distributed under the terms and conditions of the MIT License ' +# (see LICENSE file at the root of this source code package). ' +# ' +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +import numpy as np +from geoh5py.objects import Surface +from octree_creation_app.driver import OctreeDriver +from octree_creation_app.params import OctreeParams + + +def get_topo_mesh(workspace): + vertices = np.array( + [ + [0.0, 0.0, 0.0], + [10.0, 0.0, 0.0], + [10.0, 10.0, 0.0], + [0.0, 10.0, 0.0], + ] + ) + cells = np.array([[0, 1, 2], [0, 2, 3]]) + + topography = Surface.create(workspace, name="topo", vertices=vertices, cells=cells) + + kwargs = { + "geoh5": workspace, + "objects": topography, + "u_cell_size": 0.5, + "v_cell_size": 0.5, + "w_cell_size": 0.5, + "horizontal_padding": 10.0, + "vertical_padding": 10.0, + "depth_core": 5.0, + "minimum_level": 4, + "diagonal_balance": False, + "refinements": [ + { + "refinement_object": topography, + "levels": [4, 2, 1], + "horizon": True, + } + ], + } + params = OctreeParams(**kwargs) + params.write_ui_json(workspace.h5file.parent / "octree.ui.json") + driver = OctreeDriver(params) + octree = driver.run() + return topography, octree diff --git a/tests/plate_simulation/models/events_test.py b/tests/plate_simulation/models/events_test.py new file mode 100644 index 00000000..ed931533 --- /dev/null +++ b/tests/plate_simulation/models/events_test.py @@ -0,0 +1,114 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2025 Mira Geoscience Ltd. ' +# ' +# This file is part of simpeg-drivers package. ' +# ' +# simpeg-drivers is distributed under the terms and conditions of the MIT License ' +# (see LICENSE file at the root of this source code package). ' +# ' +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +import numpy as np +from geoh5py import Workspace +from geoh5py.objects import Surface + +from simpeg_drivers.plate_simulation.models.events import ( + Anomaly, + Deposition, + Erosion, + Overburden, +) +from simpeg_drivers.plate_simulation.models.options import PlateOptions +from simpeg_drivers.plate_simulation.models.parametric import Plate + +from . import get_topo_mesh + + +def test_deposition(tmp_path): + with Workspace(tmp_path / "test.geoh5") as ws: + topography, octree = get_topo_mesh(ws) + locs = topography.vertices.copy() + locs[:, 2] -= 5.0 + surface = Surface.create(ws, vertices=locs, cells=topography.cells) + + deposition = Deposition(surface=surface, value=2.0, name="deposition") + background = np.ones(octree.n_cells) + event_map = {1: ("Background", 1.0)} + deposition_model, event_map = deposition.realize( + mesh=octree, model=background, event_map=event_map + ) + for event_id, props in event_map.items(): + deposition_model[deposition_model == event_id] = props[1] + model = octree.add_data({"model": {"values": deposition_model}}) + + ind = octree.centroids[:, 2] < -5.0 + assert all(model.values[ind] == 2) + assert all(model.values[~ind] == 1) + + +def test_erosion(tmp_path): + with Workspace(tmp_path / "test.geoh5") as ws: + topography, octree = get_topo_mesh(ws) + erosion = Erosion(surface=topography) + background = np.ones(octree.n_cells) + event_map = {1: ("Background", 1.0)} + erosion_model, event_map = erosion.realize( + mesh=octree, model=background, event_map=event_map + ) + for event_id, props in event_map.items(): + erosion_model[erosion_model == event_id] = props[1] + model = octree.add_data({"model": {"values": erosion_model}}) + + ind = octree.centroids[:, 2] < 0.0 + assert all(np.isfinite(model.values[ind])) + assert all(np.isnan(model.values[~ind])) + + +def test_overburden(tmp_path): + with Workspace(tmp_path / "test.geoh5") as ws: + topography, octree = get_topo_mesh(ws) + overburden = Overburden(topography=topography, thickness=5.0, value=2.0) + background = np.ones(octree.n_cells) + event_map = {1: ("Background", 1.0)} + overburden_model, event_map = overburden.realize( + mesh=octree, model=background, event_map=event_map + ) + for event_id, props in event_map.items(): + overburden_model[overburden_model == event_id] = props[1] + model = octree.add_data({"model": {"values": overburden_model}}) + + ind = octree.centroids[:, 2] < -5.0 + assert all(model.values[ind] == 1) + assert all(model.values[~ind] == 2) + + +def test_anomaly(tmp_path): + with Workspace(tmp_path / "test.geoh5") as workspace: + _, octree = get_topo_mesh(workspace) + params = PlateOptions( + name="my plate", + plate=10.0, + elevation=-1.5, + width=10.0, + strike_length=10.0, + dip_length=1.0, + ) + plate = Plate(params, center=(5.0, 5.0, -1.5)) + + anomaly = Anomaly(body=plate, value=10.0) + event_map = {1: ("Background", 1.0)} + model, event_map = anomaly.realize( + mesh=octree, model=np.ones(octree.n_cells), event_map=event_map + ) + for event_id, props in event_map.items(): + model[model == event_id] = props[1] + data = octree.add_data({"model": {"values": model}}) + ind = ( + (octree.centroids[:, 0] > 0.0) + & (octree.centroids[:, 0] < 10.0) + & (octree.centroids[:, 1] > 0.0) + & (octree.centroids[:, 1] < 10.0) + & (octree.centroids[:, 2] > -2.0) + & (octree.centroids[:, 2] < -1.0) + ) + assert all(data.values[ind] == 10.0) diff --git a/tests/plate_simulation/models/params_test.py b/tests/plate_simulation/models/params_test.py new file mode 100644 index 00000000..a07dbde3 --- /dev/null +++ b/tests/plate_simulation/models/params_test.py @@ -0,0 +1,80 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2025 Mira Geoscience Ltd. ' +# ' +# This file is part of simpeg-drivers package. ' +# ' +# simpeg-drivers is distributed under the terms and conditions of the MIT License ' +# (see LICENSE file at the root of this source code package). ' +# ' +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +import numpy as np +from geoh5py import Workspace +from geoh5py.objects import Points, Surface + +from simpeg_drivers.plate_simulation.models.options import PlateOptions + + +def test_plate_params(tmp_path): + workspace = Workspace(tmp_path / "test.geoh5") + params = PlateOptions( + name="my plate", + plate=1.0, + width=20.0, + strike_length=1500.0, + dip_length=400.0, + dip=90.0, + dip_direction=0.0, + reference="center", + number=1, + spacing=10.0, + relative_locations=True, + easting=10.0, + northing=10.0, + elevation=-100.0, + reference_surface="topography", + reference_type="mean", + ) + assert params.spacing == 0.0 + + survey = Points.create( + workspace, + name="survey", + vertices=np.array([[-10, -10, 0]]), + ) + topography = Surface.create( + workspace, + name="test", + vertices=np.array([[-1, -1, 0], [1, -1, 0], [1, 1, 0], [-1, 1, 0]]), + cells=np.array([[0, 1, 2], [0, 2, 3]]), + ) + + center = params.center(survey, topography) + assert np.allclose(center, [0, 0, -300]) + + params.relative_locations = False + center = params.center(survey, topography) + assert np.allclose(center, [10, 10, -100]) + + +def test_plate_params_empty_reference(): + params = PlateOptions( + name="my plate", + plate=1.0, + width=20.0, + strike_length=1500.0, + dip_length=400.0, + dip=90.0, + dip_direction=0.0, + reference="center", + number=1, + spacing=10.0, + relative_locations=True, + easting=10.0, + northing=10.0, + elevation=-100.0, + reference_surface=None, + reference_type=None, + ) + assert params.reference_surface == "topography" + assert params.reference_type == "mean" diff --git a/tests/plate_simulation/models/plates_test.py b/tests/plate_simulation/models/plates_test.py new file mode 100644 index 00000000..2e5a8d44 --- /dev/null +++ b/tests/plate_simulation/models/plates_test.py @@ -0,0 +1,142 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2025 Mira Geoscience Ltd. ' +# ' +# This file is part of simpeg-drivers package. ' +# ' +# simpeg-drivers is distributed under the terms and conditions of the MIT License ' +# (see LICENSE file at the root of this source code package). ' +# ' +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +import numpy as np +from geoapps_utils.utils.transformations import rotate_xyz +from geoh5py import Workspace + +from simpeg_drivers.plate_simulation.driver import PlateSimulationDriver +from simpeg_drivers.plate_simulation.models.options import PlateOptions +from simpeg_drivers.plate_simulation.models.parametric import Plate + + +def are_collocated(pts1, pts2): + truth = [] + for loc in pts1: + truth.append(any(np.allclose(loc, k) for k in pts2)) + + return np.all(truth) + + +def vertical_east_striking_plate(): + params = PlateOptions( + name="my plate", + plate=1.0, + elevation=0.0, + width=10.0, + strike_length=1000.0, + dip_length=500.0, + dip=90.0, + dip_direction=0.0, + ) + plate = Plate(params) + + return plate.surface + + +def test_vertical_east_striking_plate(): + vertical_east_striking = vertical_east_striking_plate() + assert vertical_east_striking.vertices is not None + assert vertical_east_striking.extent is not None + assert np.isclose( + vertical_east_striking.extent[1, 0] - vertical_east_striking.extent[0, 0], + 1000.0, + ) + assert np.isclose( + vertical_east_striking.extent[1, 1] - vertical_east_striking.extent[0, 1], + 10.0, + ) + assert np.isclose( + vertical_east_striking.extent[1, 2] - vertical_east_striking.extent[0, 2], + 500.0, + ) + assert ( + vertical_east_striking.vertices[:, 0].mean() == 0.0 # pylint: disable=no-member + ) + assert ( + vertical_east_striking.vertices[:, 1].mean() == 0.0 # pylint: disable=no-member + ) + assert ( + vertical_east_striking.vertices[:, 2].mean() == 0.0 # pylint: disable=no-member + ) + + +def test_dipping_plates_all_quadrants(): + reference = vertical_east_striking_plate() + + for dip_direction in np.arange(0.0, 361.0, 45.0): + for dip in [20.0, 70.0]: + params = PlateOptions( + name=f"plate dipping {dip} at {dip_direction}", + plate=1.0, + elevation=0.0, + width=10.0, + strike_length=1000.0, + dip_length=500.0, + dip=dip, + dip_direction=dip_direction, + reference="center", + ) + + plate = Plate(params) + surface = plate.surface + locs = rotate_xyz(surface.vertices, [0.0, 0.0, 0.0], dip_direction, 0.0) + locs = rotate_xyz(locs, [0.0, 0.0, 0.0], 0.0, dip - 90.0) + assert np.allclose(locs, reference.vertices) + + +def test_replicate_even(tmp_path): + workspace = Workspace.create(tmp_path / f"{__name__}.geoh5") + options = PlateOptions( + name="test", + plate=1.0, + width=1.0, + strike_length=1.0, + dip_length=1.0, + elevation=1.0, + ) + plate = Plate(options, (0, 0, 0), workspace=workspace) + plates = PlateSimulationDriver.replicate(plate, 2, 10.0, 90.0) + assert plates[0].surface.vertices is not None + assert plates[1].surface.vertices is not None + assert plates[0].params.name == "test offset 1" + assert np.allclose( + plates[0].surface.vertices.mean(axis=0), np.array([-5.0, 0.0, 0.0]) + ) + assert plates[1].params.name == "test offset 2" + assert np.allclose( + plates[1].surface.vertices.mean(axis=0), np.array([5.0, 0.0, 0.0]) + ) + + +def test_replicate_odd(tmp_path): + workspace = Workspace.create(tmp_path / f"{__name__}.geoh5") + options = PlateOptions( + name="test", + plate=1.0, + width=1.0, + strike_length=1.0, + dip_length=1.0, + elevation=1.0, + ) + plate = Plate(options, (0, 0, 0), workspace=workspace) + plates = PlateSimulationDriver.replicate(plate, 3, 5.0, 0.0) + assert plates[0].surface.vertices is not None + assert plates[1].surface.vertices is not None + assert plates[2].surface.vertices is not None + assert np.allclose( + plates[0].surface.vertices.mean(axis=0), np.array([0.0, -5.0, 0.0]) + ) + assert np.allclose( + plates[1].surface.vertices.mean(axis=0), np.array([0.0, 0.0, 0.0]) + ) + assert np.allclose( + plates[2].surface.vertices.mean(axis=0), np.array([0.0, 5.0, 0.0]) + ) diff --git a/tests/plate_simulation/models/series_test.py b/tests/plate_simulation/models/series_test.py new file mode 100644 index 00000000..c8be72e9 --- /dev/null +++ b/tests/plate_simulation/models/series_test.py @@ -0,0 +1,150 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2025 Mira Geoscience Ltd. ' +# ' +# This file is part of simpeg-drivers package. ' +# ' +# simpeg-drivers is distributed under the terms and conditions of the MIT License ' +# (see LICENSE file at the root of this source code package). ' +# ' +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +# pylint: disable=too-many-locals + +import numpy as np +import pytest +from geoh5py import Workspace +from geoh5py.objects import Surface + +from simpeg_drivers.plate_simulation.models.events import ( + Deposition, + Erosion, + Overburden, +) +from simpeg_drivers.plate_simulation.models.series import ( + Geology, + GeologyViolationError, + Lithology, +) + +from . import get_topo_mesh + + +def test_lithology(tmp_path): + with Workspace(tmp_path / "test.geoh5") as ws: + _, octree = get_topo_mesh(ws) + surfaces = {} + for n_layer, elevation in enumerate([-2.0, -5.0, -10.0]): + vertices = np.array( + [ + [0.0, 0.0, elevation], + [10.0, 0.0, elevation], + [10.0, 10.0, elevation], + [0.0, 10.0, elevation], + ] + ) + cells = np.array([[0, 1, 2], [0, 2, 3]]) + + surfaces[f"layer{n_layer + 1}"] = Surface.create( + ws, name="topo", vertices=vertices, cells=cells + ) + + lithology = Lithology( + history=[ + Deposition(surface=surfaces["layer3"], value=3.0), + Deposition(surface=surfaces["layer2"], value=2.0), + Deposition(surface=surfaces["layer1"], value=1.0), + ] + ) + event_map = {0: ("Backgrounds", 0.0)} + lithology_model, event_map = lithology.realize( + mesh=octree, model=np.zeros(octree.n_cells), event_map=event_map + ) + for event_id, props in event_map.items(): + lithology_model[lithology_model == event_id] = props[1] + + model = octree.add_data({"model": {"values": lithology_model}}) + + assert len({val[0] for val in event_map.values()}) == 4 # All unique names + assert all(model.values[octree.centroids[:, 2] > -2.0] == 0.0) + ind_layer_1 = (octree.centroids[:, 2] < -2.0) & (octree.centroids[:, 2] > -5.0) + assert all(model.values[ind_layer_1] == 1.0) + ind_layer_2 = (octree.centroids[:, 2] < -5.0) & (octree.centroids[:, 2] > -10.0) + assert all(model.values[ind_layer_2] == 2.0) + assert all(model.values[octree.centroids[:, 2] < -10.0] == 3.0) + + +def test_scenario(tmp_path): + with Workspace(tmp_path / "test.geoh5") as ws: + topography, octree = get_topo_mesh(ws) + surfaces = {} + for n_layer, elevation in enumerate([-2.0, -5.0, -10.0]): + surfaces[f"layer{n_layer + 1}"] = Surface.create( + ws, + name="topo", + vertices=np.array( + [ + [0.0, 0.0, elevation], + [10.0, 0.0, elevation], + [10.0, 10.0, elevation], + [0.0, 10.0, elevation], + ] + ), + cells=np.array([[0, 1, 2], [0, 2, 3]]), + ) + + lithology = Lithology( + history=[ + Deposition(surface=surfaces["layer3"], value=3.0), + Deposition(surface=surfaces["layer2"], value=2.0), + Deposition(surface=surfaces["layer1"], value=1.0), + ] + ) + overburden = Overburden(topography=topography, thickness=1.0, value=10.0) + erosion = Erosion(surface=topography) + + with pytest.raises( + GeologyViolationError, + match="Overburden events must occur before the final erosion in the history.", + ): + Geology( + workspace=ws, + mesh=octree, + background=0.0, + history=[lithology, erosion, overburden], + ) + + with pytest.raises( + GeologyViolationError, + match="The last event in a geological history must be an erosion.", + ): + Geology( + workspace=ws, + mesh=octree, + background=0.0, + history=[overburden, lithology], + ) + + scenario = Geology( + workspace=ws, + mesh=octree, + background=100.0, + history=[lithology, overburden, erosion], + ) + model, event_map = scenario.build() + assert model is not None + + for event_id, props in event_map.items(): + model[model == event_id] = props[1] + + ind = octree.centroids[:, 2] > 0.0 + assert all(np.isnan(model[ind])) + ind = (octree.centroids[:, 2] < 0.0) & (octree.centroids[:, 2] > -1.0) + assert all(model[ind] == 10.0) + ind = (octree.centroids[:, 2] < -1.0) & (octree.centroids[:, 2] > -2.0) + assert all(model[ind] == 100.0) + ind = (octree.centroids[:, 2] < -2.0) & (octree.centroids[:, 2] > -5.0) + assert all(model[ind] == 1.0) + ind = (octree.centroids[:, 2] < -5.0) & (octree.centroids[:, 2] > -10.0) + assert all(model[ind] == 2.0) + ind = octree.centroids[:, 2] < -10.0 + np.testing.assert_allclose(model[ind], 3.0) diff --git a/tests/plate_simulation/runtest/__init__.py b/tests/plate_simulation/runtest/__init__.py new file mode 100644 index 00000000..4d06f672 --- /dev/null +++ b/tests/plate_simulation/runtest/__init__.py @@ -0,0 +1,9 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2025 Mira Geoscience Ltd. ' +# ' +# This file is part of simpeg-drivers package. ' +# ' +# simpeg-drivers is distributed under the terms and conditions of the MIT License ' +# (see LICENSE file at the root of this source code package). ' +# ' +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' diff --git a/tests/plate_simulation/runtest/driver_test.py b/tests/plate_simulation/runtest/driver_test.py new file mode 100644 index 00000000..b85d7736 --- /dev/null +++ b/tests/plate_simulation/runtest/driver_test.py @@ -0,0 +1,129 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2025 Mira Geoscience Ltd. ' +# ' +# This file is part of simpeg-drivers package. ' +# ' +# simpeg-drivers is distributed under the terms and conditions of the MIT License ' +# (see LICENSE file at the root of this source code package). ' +# ' +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +import numpy as np +from geoh5py import Workspace +from geoh5py.groups import SimPEGGroup +from geoh5py.objects import AirborneTEMReceivers, ObjectBase, Octree, Surface +from geoh5py.ui_json import InputFile + +from simpeg_drivers import assets_path +from simpeg_drivers.electromagnetics.time_domain.options import TDEMForwardOptions +from simpeg_drivers.plate_simulation.driver import ( + PlateSimulationDriver, + PlateSimulationOptions, +) +from simpeg_drivers.plate_simulation.models.options import ModelOptions +from simpeg_drivers.plate_simulation.options import MeshOptions +from simpeg_drivers.potential_fields.gravity.options import GravityForwardOptions +from tests.testing_utils import setup_inversion_workspace + + +# pylint: disable=too-many-statements +def test_plate_simulation_params_from_input_file(tmp_path): + geoh5, mesh, model, survey, topography = setup_inversion_workspace( + tmp_path, + background=0.0, + anomaly=0.0, + n_electrodes=8, + n_lines=8, + inversion_type="gravity", + flatten=False, + ) + + with geoh5.open() as ws: + ifile = InputFile.read_ui_json( + assets_path() / "uijson" / "plate_simulation.ui.json", validate=False + ) + ifile.data["name"] = "test_gravity_plate_simulation" + ifile.data["geoh5"] = ws + + # Add simulation parameter + gravity_inversion = SimPEGGroup.create(ws) + + options = GravityForwardOptions.model_construct() + fwr_ifile = InputFile.read_ui_json(options.default_ui_json) + options_dict = fwr_ifile.ui_json + options_dict["inversion_type"] = "gravity" + options_dict["forward_only"] = True + options_dict["geoh5"] = str(ws.h5file) + options_dict["topography_object"]["value"] = str(topography.uid) + options_dict["data_object"]["value"] = str(survey.uid) + gravity_inversion.options = options_dict + ifile.data["simulation"] = gravity_inversion + + # Add mesh parameters + ifile.data["u_cell_size"] = 10.0 + ifile.data["v_cell_size"] = 10.0 + ifile.data["w_cell_size"] = 10.0 + ifile.data["depth_core"] = 400.0 + ifile.data["minimum_level"] = 8 + ifile.data["max_distance"] = 200.0 + ifile.data["diagonal_balance"] = False + ifile.data["padding_distance"] = 1500.0 + + # Add model parameters + ifile.data["background"] = 1000.0 + ifile.data["overburden"] = 5.0 + ifile.data["thickness"] = 50.0 + ifile.data["plate"] = 2.0 + ifile.data["width"] = 100.0 + ifile.data["strike_length"] = 100.0 + ifile.data["dip_length"] = 100.0 + ifile.data["dip"] = 0.0 + ifile.data["dip_direction"] = 0.0 + ifile.data["number"] = 9 + ifile.data["spacing"] = 10.0 + ifile.data["relative_locations"] = True + ifile.data["easting"] = 10.0 + ifile.data["northing"] = 10.0 + ifile.data["elevation"] = -250 + ifile.data["reference_surface"] = "topography" + ifile.data["reference_type"] = "mean" + + params = PlateSimulationOptions.build(ifile) + assert isinstance(params.simulation, SimPEGGroup) + + simulation_parameters = params.simulation_parameters() + + assert simulation_parameters.inversion_type == "gravity" + assert simulation_parameters.forward_only + assert simulation_parameters.geoh5.h5file == ws.h5file + assert simulation_parameters.active_cells.topography_object.uid == topography.uid + assert simulation_parameters.data_object.uid == survey.uid + + assert isinstance(params.mesh, MeshOptions) + assert params.mesh.u_cell_size == 10.0 + assert params.mesh.v_cell_size == 10.0 + assert params.mesh.w_cell_size == 10.0 + assert params.mesh.depth_core == 400.0 + assert params.mesh.max_distance == 200.0 + assert params.mesh.padding_distance == 1500.0 + assert params.mesh.minimum_level == 8 + assert not params.mesh.diagonal_balance + + assert isinstance(params.model, ModelOptions) + assert params.model.plate_model.name == "test_gravity_plate_simulation" + assert params.model.background == 1000.0 + assert params.model.overburden_model.thickness == 50.0 + assert params.model.overburden_model.overburden == 5.0 + assert params.model.plate_model.plate == 2.0 + assert params.model.plate_model.width == 100.0 + assert params.model.plate_model.strike_length == 100.0 + assert params.model.plate_model.dip_length == 100.0 + assert params.model.plate_model.dip == 0.0 + assert params.model.plate_model.dip_direction == 0.0 + + assert params.model.plate_model.number == 9 + assert params.model.plate_model.spacing == 10.0 + assert params.model.plate_model.relative_locations + assert params.model.plate_model.easting == 10.0 + assert params.model.plate_model.northing == 10.0 + assert params.model.plate_model.elevation == -250.0 diff --git a/tests/plate_simulation/runtest/gravity_test.py b/tests/plate_simulation/runtest/gravity_test.py new file mode 100644 index 00000000..40d796ab --- /dev/null +++ b/tests/plate_simulation/runtest/gravity_test.py @@ -0,0 +1,89 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2025 Mira Geoscience Ltd. ' +# ' +# This file is part of simpeg-drivers package. ' +# ' +# simpeg-drivers is distributed under the terms and conditions of the MIT License ' +# (see LICENSE file at the root of this source code package). ' +# ' +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +import numpy as np +from geoh5py import Workspace +from geoh5py.groups import SimPEGGroup + +from simpeg_drivers.plate_simulation.driver import PlateSimulationDriver +from simpeg_drivers.plate_simulation.models.options import ( + ModelOptions, + OverburdenOptions, + PlateOptions, +) +from simpeg_drivers.plate_simulation.options import MeshOptions, PlateSimulationOptions +from simpeg_drivers.potential_fields.gravity.options import GravityForwardOptions +from tests.testing_utils import setup_inversion_workspace + + +def test_gravity_plate_simulation(tmp_path): + geoh5, mesh, model, survey, topography = setup_inversion_workspace( + tmp_path, + background=0.0, + anomaly=0.0, + n_electrodes=8, + n_lines=8, + inversion_type="gravity", + flatten=False, + ) + + with geoh5.open() as ws: + mesh_params = MeshOptions( + u_cell_size=10.0, + v_cell_size=10.0, + w_cell_size=10.0, + padding_distance=1500.0, + depth_core=600.0, + max_distance=200.0, + ) + + overburden_params = OverburdenOptions(thickness=50.0, overburden=0.2) + + plate_params = PlateOptions( + name="plate", + plate=0.5, + elevation=-250.0, + width=100.0, + strike_length=100.0, + dip_length=100.0, + dip=0.0, + dip_direction=0.0, + reference="center", + ) + + model_params = ModelOptions( + name="density", + background=0.0, + overburden_model=overburden_params, + plate_model=plate_params, + ) + + options = GravityForwardOptions.build( + topography_object=topography, + data_object=survey, + geoh5=ws, + starting_model=0.1, + ) + + gravity_inversion = SimPEGGroup.create(ws) + gravity_inversion.options = options.serialize() + + params = PlateSimulationOptions( + title="test", + run_command="run", + geoh5=ws, + mesh=mesh_params, + model=model_params, + simulation=gravity_inversion, + ) + driver = PlateSimulationDriver(params) + driver.run() + + assert np.nanmax(driver.model.values) == 0.5