diff --git a/.github/workflows/issue_to_jira.yml b/.github/workflows/issue_to_jira.yml index a6b13cb04..b031676cd 100644 --- a/.github/workflows/issue_to_jira.yml +++ b/.github/workflows/issue_to_jira.yml @@ -17,5 +17,5 @@ jobs: components: '[{"name": "simpeg"}]' secrets: JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} - JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} - JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} + JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN_WRITE }} + JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL_WRITE }} diff --git a/.github/workflows/pr_jira_actions.yml b/.github/workflows/pr_jira_actions.yml index ed9916b8e..084ddfcad 100644 --- a/.github/workflows/pr_jira_actions.yml +++ b/.github/workflows/pr_jira_actions.yml @@ -6,7 +6,6 @@ on: jobs: call-workflow-pr_jira_actions: - if: github.event.action != 'edited' || github.event.changes.title != null uses: MiraGeoscience/CI-tools/.github/workflows/reusable-jira-pr_actions.yml@v3 permissions: contents: read diff --git a/.github/workflows/python_analysis.yml b/.github/workflows/python_analysis.yml index 32c101942..9c8479cbd 100644 --- a/.github/workflows/python_analysis.yml +++ b/.github/workflows/python_analysis.yml @@ -20,7 +20,7 @@ on: permissions: {} concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: diff --git a/.github/workflows/security_scan.yml b/.github/workflows/security_scan.yml index 18543a731..bda779d45 100644 --- a/.github/workflows/security_scan.yml +++ b/.github/workflows/security_scan.yml @@ -20,7 +20,7 @@ on: permissions: {} concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: diff --git a/.gitignore b/.gitignore index c826ed127..8fbfcc930 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -# Byte-compiled / optimized / DLL files +# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a6c522bd5..38d7aeb87 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: - id: pycln args: [--config=pyproject.toml] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.15 + rev: v0.15.20 hooks: - id: ruff-check args: @@ -42,7 +42,7 @@ repos: # ] # exclude: ^(docs|simpeg_drivers-assets)/ - repo: https://github.com/codingjoe/relint - rev: 3.3.1 + rev: 3.5.0 hooks: - id: relint args: [-W] # to fail on warnings diff --git a/docs/_toc.yml b/docs/_toc.yml index e776065b3..f5c32be4c 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -44,4 +44,5 @@ chapters: - file: case_studies/Forrestania/python_code/unconstrained_gravity_inv_training - file: case_studies/Forrestania/python_code/unconstrained_magnetics_inv_training - file: case_studies/Forrestania/python_code/joint_grav_mag +- file: release_notes - file: THIRD_PARTY_SOFTWARE diff --git a/docs/plate-simulation/images/methodology/data/simpeg_group_options.png b/docs/plate-simulation/images/methodology/data/simpeg_group_options.png index f38bb63a9..06e41ca8a 100644 Binary files a/docs/plate-simulation/images/methodology/data/simpeg_group_options.png and b/docs/plate-simulation/images/methodology/data/simpeg_group_options.png differ diff --git a/docs/plate-simulation/images/methodology/uijson.png b/docs/plate-simulation/images/methodology/uijson.png index 1bd5cf919..a588f784b 100644 Binary files a/docs/plate-simulation/images/methodology/uijson.png and b/docs/plate-simulation/images/methodology/uijson.png differ diff --git a/docs/plate-simulation/standalone.md b/docs/plate-simulation/standalone.md index c89164dfe..c70600ada 100644 --- a/docs/plate-simulation/standalone.md +++ b/docs/plate-simulation/standalone.md @@ -26,13 +26,14 @@ The parameters controlling the forward simulation are defined through a SimPEG f must ensure that the SimPEG group has been previously edited with appropriate options, including a topography object, a survey object and data components to simulate. +Oftentimes, the user would like to model a thin plate that might push the resolution limits of the finite-volume approach taken by SimPEG. In this case, the LeroiAir application may be used instead to model the secondary fields from a rectangular prism with a known conductivity-thickness. To use the LeroiAir application for the forward modelling, select the ``Use LeroiAir`` checkbox. + ```{figure} images/methodology/data/simpeg_group_options.png --- width: 300pt align: center --- -Selecting the initialized forward modelling SimPEG group and naming the group that will store the plate -simulation results. +Selecting the initialized forward modelling SimPEG group, or optionally choosing LeroiAir for improved thin plate modelling and providing a name for the results group. ``` - Create the required SimPEG group within Geoscience ANALYST through the diff --git a/docs/release_notes.md b/docs/release_notes.md new file mode 100644 index 000000000..394937a0a --- /dev/null +++ b/docs/release_notes.md @@ -0,0 +1,258 @@ +# Release Notes + +## Release 0.5.0 (2026-06-05) + +### Plate Simulation +* Create UI and Driver for the classification of EM anomalies +* Export best match models as Maxwell plates (w/ GEOPY-2661) +* Finalize Anglo Accelerated Development with synthetic example +* Add documentation for plate sweep and match +* Add summary option for plate sweep +* Change reference of Plate from center-center to top-center +* Plate Match: Attach figure with comparative plot of scaled obs versus simulated data +* Make simpeg group names in plate-simulation more clear in the ui.jsons +### Mag/Grav +* MVI: Add small value to reference amplitude if reference are used +* Implement MVI with PDE inversion +* Use cyclic colormap by default for declination angle model +* Change data groups saved for MVI as requested by users +* Revert legacy behaviour with reference model ON by default +### FEM/TEM +* Implement arbitrary receiver orientations (u-v-w) for airborne EM surveys +* Relabel ui.json for TEM using Vertical, inLine, cross-line +* Airborne FEM coaxial do not rotate transmitter, and ppm normalization needs to be adjusted +* Allow rotated receivers for ground EM survey +* Remove dependency on Tx frequency for airborne FEM survey +### Natural Sources +* Incorporate MobileMT inversion with SimPEG (apparent conductivity only) DC/IP +### DC/IP +* Parallelize batch 2D inversion +* Improve topography augmentation for DC and MT +* Accept 2D inversion mesh (DrapeModel) in sensitivity_cutoff application +* Add tooltips to missing IP inversion inputs +* DC2D with single line crashes on run with n_workers set +* Investigate edge cells recovered with DC2D inversions +### Joint Inversion +* Joint surveys using mvi crashes with dimension mismatch +* PGI: Add checks for reference model present on inversion groups. Return clean GeoAppsError if not +* Research auto-scaling strategy for cross-gradient regularization terms +* Membership flip between iterations in PGI +* Add PGI documentation +### General Utilities +* Add dip_direction to list of supported variables by plate_sweep +* Micro-management of auto-scaling of misfits with tiles, channels and within joint inversions +* Clip limits of topography extent based on mesh extent +* Add datetime stamp to simpeg.log and simpeg.out file names +* Add validator for negative on NDV values in uncertainties +* Transfer visual parameters Core volume settings from the input mesh to the inversion mesh (if present) +* Add optional out_group to Depth of Investigation app +### Bugs and Maintenance +* Update GravityUIJson with new data forms +* Conductivity/Resistivity switcher doesn't make sense for joint mvi/mag +* Synthetic topography for runtests is overly sampled +* Fix capitalization in labels and tooltips of ui.json files +* Update minimum requirement to python >=3.12, <3.14 and numpy 2.* +* Implement direct run_commands to specific modules for forwards and inversions +* DCIP 2D crash on line ID selection +* PGI failure on petrophysical model with air cells +* Inversion stalls on tiling for large problems during redistribution of clusters +* Refactor extent property calculation for all grid objects +* Consolidate code for plate model definition between different repos +* Update dictionary to choose driver from run_command +* Triangulation crash on input surface +* Regularization parameters of sub-drivers always over-written by joint parameters +* Inversion crash on tile mesh creation transferring data +* Refresh screenshots of UIJsons in docs + +## Release 0.4.0 (2026-01-06) + +* Support 3D vector property group for orientation of rotated gradients +* Add rotated_gradient to 2D inversions +* Refactor components of inversion options +* single entry point to run any application +* Joint inversion: PGI +* Migrate param-sweeps to geoapps-utils +* Migrate plate-simulation to simpeg-drivers +* Hook up 2D rotated gradients to batch 2D inversions +* Investigate random failure of cross-gradient test +* Amplitude model stuck on upper bound for MVI +* Change default sensitivity threshold for DC inversions +* Fix plate simulation label and tooltip +* Speed up saving directive by keeping the file open during series +* Change units displayed on selector of data type for V/Am^2 +* Investigate slow startup time for TEM inversion +* Make pydantic validator issues returns clean GeoAppsError option +* Add case study with Forestania datasets +* Improve rotated gradient on octree change with average cell dim +* Add files via upload +* Add docs on rotated gradient options +* Force use of MKL for Blas implementation +* Bad flipping of large loop orientation +* Refactor of the Factory classes +* Migrate octree-creation-app to grid-app +* Investigate double-print of Geoapps-Error +* Can't handle topography grid with nan +* Parallelize 1D simulations +* Forward simulation of potential fields always forms the full J +* Re-order parameters to mimic the order in metadata +* Add check for negative cond/res model values +* Lost control on reference angles for MVI +* Add core depth to the max-min elevation of the survey +* Detect issues with waveform and throw a custom GeoappsError if found +* Add a copy group+object base in input file as it exists in the UIJson group +* Failure of directives when using Futures for simulation +* Create custom sweep UIjson for plate-simulation +* Investigate speckly looking MVI models with rotated gradients +* Make start/stop group optional, and use the plate-sim parameter instead if disabled +* Missing grouping of EM data in save directive for joint inversions +* Parallelize the Sweeps +* Explore 2D gridding techniques for EM line data +* Pydantic error on auto-mesh creation with Grid2D topography object +* Crash on Zarr file shape for tiled inversions with disk storage +* Crash on no-upper bound for MVI within a joint process +* Improve parallel creation of 1D simulations +* Investigate erratic corner cells when using rotated gradients +* Add custom error for topography grid selected without data +* Random failure of sensitivity cutoff app tests +* Workers crash on creating misfits for large problems +* Remove save directive for petrophysical model +* Adjust target misfit down to account for number of NDV in data +* PGI fails with single simulation selected +* MT predicted channels missing [] indices +* Break edge case for 1D inversion with negative elevation (release 4.7) +* Save sensitivities option ignored (release 4.7) +* Wrong normalization for MT +* Speed up active from topography algorithm +* Bring in doc of plate simulation +* Remove out_group form ui.json +* Cannot load results from Batch 2D inversion runs +* DEVOPS-922: adjust or fix text in ui.json +* Check for MVI Simpeg groups and raise error +* Joint surveys fails validation if no starting/reference models chosen + +## Release 0.3.0 (2025-06-20) + +* Create spatial tiling estimator +* Add a depth of investigation application +* Add auto-meshing option for all inversions with default parameters +* Fix simpeg-drivers pyproject still pointing to octree-creation-app/* +* Auto-meshing: Round cell size to nearest 5 multiplier +* Allow option for percentile cutoff in sensitivity based depth of investigation +* Auto-meshing: Only use depth_code (no vertical pads) for potential fields and DC-IP inversions +* Add UIJson group to hold octree creation parameters and update options +* Octree mesh upside down +* Refactor Params class with BaseData +* Convert all of potential fields to BaseData params class +* Faulty air cells in 2D inversion active +* Reduce tests runtime +* Use MetaSimulations instead of custom misfit mapping towards distributed process +* Convert all DC/IP 3D/2D params to BaseData +* Convert all EM params to BaseData +* Convert both joint params to BaseData +* Export uml from simpeg-drivers and design class structure for inversion parameters. +* Selecting an active model instead of topography with surface survey option crashes +* Update discretize to >=0.11.* +* Cleanup constant files and data classes +* Add version validation and write_default method to update version in … +* Update SimPEG 0.21.2 to 0.23 +* Crash running 2D IP +* Add custom Error for problem too large +* Add UIJson class for gravity forward/inversion +* Add option for MUMPS solver +* Duplicated survey object when monitoring_directory is used +* Resolve deprecation warnings with latest simpeg 0.23 and discretize 0.11 +* Create documentation for depth of investigation app +* Fixup dependencies +* Draft uml file for the UIJson/Params design +* Update inversion docs to describe the auto-mesh option +* Phi-d at Iter 0 shows as nan in the *.out file +* Handle extra fields and deprecations in UIJson version validation +* Clean up pydantic warnings +* Implement structural orientation (rotated gradient) option to the inversion UI +* Migrate em_inversion (EM1D) script to simpeg-drivers +* Should update version when writing ui.json from simpeg-drivers options classes +* Migrate FEM-1D from simpeg-drivers +* Make lower bound for MVI visible=False +* Add strike/dip option to the choice list of rotated_gradients. +* Deprecate gradient_type +* Fix warning about differing number of parameters between ui_json and data. +* Relock on latest git dev revisions +* Environment for coming pre-release +* Fix UI json version tests (part of GEOPY-2049) +* Rename params modules to options +* Group models for L2 and LP +* Add version to print screen +* Allow IntegerData for active cell value model type +* Use published dependencies, no git branches +* Correctly declare dask for pip and dask-core for conda +* Fixup dask dependency: do not lock on pypi +* Use dask.distributed with workers as default behaviour +* IP inversion with resistivity option does not convert the model to conductivity +* Name of conductivity_model does not match the selection for resistivity +* Change default auto-scaling to false +* Bring back adjusted ui.json from Analyst to their respective repo +* Bad referencing to transmitter id name +* MVI name change breaks the forward +* Fem1d title +* Outdated inversion_type for 3D FDEM +* Non-zero evaluation of the rotated gradient of a constant model +* Sub-mesh reused on split data +* Ensure backward compatibility of saved ui.json from Analyst 4.5 to 4.6 + +## Release 0.2.0 (2025-02-07) + +* Octree mesh cell definition not update if rec array +* Make the canny add_data optional on get_edges method +* Crash on joint survey with apparent resistivity +* Update simpeg fork to 0.21.2 +* Streamline geoapps-utils +* Generate tutorial for Natural source EM +* Add checks on waveform before sending to SimPEG +* Training material for ATEM +* Always check/closed EM loops and return warning +* Fix handling of UBC ordered Octree cells +* Add docs for validation +* Allow input active cells instead of topography +* Fix formatting of warning for LargeLoop. Use logging instead warning +* Inversion: Allow for model vector to define norms +* Re-order the inversion inputs following users comments +* Add option to export sensitivities for all inversions +* Enable cell/face weights in params +* Fill in default ui.json tooltips for inversion parameters +* Review stitching of predicted data on tipper inversion +* Review log and out for cross-gradient inversion. +* Regression: Crash with no-reference data +* Group vector model orientation parameters as "Dip Direction & Dip" +* Add option for cond/resis for all EM/DC methods +* Flip upper and lower bound value for resistivity style inversions +* Implement rescaling of misfit functions for joint inversion +* Tipper inversion crashes on background_model if not constant +* DC3D inversion reports disconnected cells +* Ram/Disk option not working in simpeg through Analyst +* Save sensitivities fails for MVI inversion in Analyst +* Nan data values not handled by the inversion +* Simpeg-drivers is adding a transmitter id to the transmitter always +* Make the transmitter id name agnostic + + +## Release 0.1.0 (2024-06-17) + +* Copy model to avoid applying double log +* prepare distribution +* Remove factor of half on cpu count for n_cpu=disabled +* Fix reduced set of linter errors on simpeg-drivers +* Regenerate env lock files (for SimPEG rc.1) +* Enforce counter-clockwise ordering of EM large loops +* Move params tests from geoapps to simpeg-drivers +* Deal with negative octree cell dimensions from legacy inversions +* Improve tiling strategy for large loop EM +* Improve scaling of cross-gradient regularization +* Build distribution files for geology and geophysics modules for Analyst +* Use new dev script to create lock files +* Fix tile ID on save directive +* New RC distribution for Analyst 4.4 +* Import of simpeg-drivers too slow +* Merge Release/0.1.0 to main +* Use octree-creation-app v0.1.1 + + Copyright (c) 2023-2026 Mira Geoscience Ltd. 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 3dfcff926..04899b376 100644 --- a/environments/py-3.12-linux-64-dev.conda.lock.yml +++ b/environments/py-3.12-linux-64-dev.conda.lock.yml @@ -1,13 +1,13 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 8ffe2b7bd402c394a70b91fad1304325a8ee68470e2f1565a7a098270cee1b60 +# input_hash: d4e89447ddc89490b9baa6153c503886506d9dbc1b81466f4cc3e6a28d370325 channels: - conda-forge - nodefaults dependencies: - _openmp_mutex=4.5=7_kmp_llvm - - _python_abi3_support=1.0=hd8ed1ab_3 + - _python_abi3_support=1.0=hd8ed1ab_2 - alabaster=0.7.16=pyhd8ed1ab_0 - annotated-types=0.7.0=pyhd8ed1ab_1 - anyio=4.14.2=pyhcf101f3_0 @@ -16,18 +16,18 @@ dependencies: - arrow=1.4.0=pyhcf101f3_0 - asciitree=0.3.3=py_2 - astroid=4.0.4=py312h7900ff3_0 - - asttokens=3.0.2=pyhd8ed1ab_0 + - asttokens=3.0.1=pyhd8ed1ab_0 - async-lru=2.3.0=pyhcf101f3_0 - attrs=26.1.0=pyhcf101f3_0 - - aws-c-auth=0.10.4=hb7a77c6_1 - - aws-c-cal=0.9.14=h2aa3ae6_4 - - aws-c-common=0.14.2=hb03c661_0 - - aws-c-compression=0.3.2=h720e601_4 - - aws-c-http=0.11.0=h38ae05a_4 - - aws-c-io=0.27.3=h6f4d18d_1 - - aws-c-s3=0.12.8=h46fcd08_1 - - aws-c-sdkutils=0.2.7=h720e601_2 - - aws-checksums=0.2.10=h720e601_4 + - aws-c-auth=0.10.3=hdc83ae9_4 + - aws-c-cal=0.9.14=h6e18c7b_3 + - aws-c-common=0.14.1=hb03c661_0 + - aws-c-compression=0.3.2=haaf8e00_3 + - aws-c-http=0.11.0=h434eaf2_3 + - aws-c-io=0.26.3=ha0b0e19_6 + - aws-c-s3=0.12.6=hd767fb6_1 + - aws-c-sdkutils=0.2.7=haaf8e00_1 + - aws-checksums=0.2.10=haaf8e00_3 - babel=2.18.0=pyhcf101f3_1 - backports.zstd=1.6.0=py312h90b7ffd_0 - beautifulsoup4=4.15.0=pyha770c72_0 @@ -38,11 +38,11 @@ dependencies: - brotli-bin=1.2.0=hb03c661_1 - brotli-python=1.2.0=py312hdb49522_1 - bzip2=1.0.8=hda65f42_9 - - c-ares=1.34.8=hb03c661_0 + - c-ares=1.34.6=hb03c661_0 - ca-certificates=2026.6.17=hbd8a1cb_0 - cached-property=1.5.2=hd8ed1ab_2 - cached_property=1.5.2=pyha770c72_2 - - certifi=2026.7.22=pyhd8ed1ab_0 + - certifi=2026.6.17=pyhd8ed1ab_0 - cffi=2.1.0=py312h460c074_0 - charset-normalizer=3.4.9=pyhd8ed1ab_0 - click=8.4.2=pyhc90fa1f_0 @@ -50,7 +50,7 @@ dependencies: - colorama=0.4.6=pyhd8ed1ab_1 - comm=0.2.3=pyhe01879c_0 - contourpy=1.3.3=py312h0a2e395_4 - - coverage=7.15.2=py312h8a5da7c_0 + - coverage=7.15.0=py312h8a5da7c_0 - cpython=3.12.13=py312hd8ed1ab_0 - cycler=0.12.1=pyhcf101f3_2 - cytoolz=1.1.0=py312h4c3975b_2 @@ -75,7 +75,7 @@ dependencies: - h11=0.16.0=pyhcf101f3_1 - h2=4.3.0=pyhcf101f3_0 - h5py=3.16.0=nompi_py312ha829cd9_102 - - hdf5=2.1.0=nompi_h654f344_110 + - hdf5=2.1.0=nompi_hc8ae080_109 - hpack=4.2.0=pyhd8ed1ab_0 - httpcore=1.0.9=pyh29332c3_0 - httpx=0.28.1=pyhd8ed1ab_0 @@ -101,24 +101,24 @@ dependencies: - jsonschema-specifications=2025.9.1=pyhcf101f3_0 - jsonschema-with-format-nongpl=4.26.0=hcf101f3_0 - jupyter-book=2.1.6=pyhcf101f3_0 - - jupyter-builder=1.1.1=pyhcf101f3_0 + - jupyter-builder=1.1.0=pyhcf101f3_0 - jupyter-lsp=2.3.1=pyhcf101f3_0 - jupyter_client=8.9.1=pyhcf101f3_0 - jupyter_core=5.9.1=pyhc90fa1f_0 - jupyter_events=0.12.1=pyhcf101f3_0 - jupyter_server=2.20.0=pyhcf101f3_0 - jupyter_server_terminals=0.5.4=pyhcf101f3_0 - - jupyterlab=4.6.2=pyhd8ed1ab_0 + - jupyterlab=4.6.1=pyhd8ed1ab_0 - jupyterlab_pygments=0.3.0=pyhd8ed1ab_2 - jupyterlab_server=2.28.0=pyhcf101f3_0 - jupyterlab_widgets=1.1.11=pyhd8ed1ab_0 - - jupytext=1.19.5=pyh0398c0e_0 + - jupytext=1.19.4=pyh0398c0e_0 - keyutils=1.6.3=hb9d3cd8_0 - kiwisolver=1.5.0=py312h0a2e395_0 - krb5=1.22.2=hbde042b_1 - lark=1.3.1=pyhd8ed1ab_0 - lcms2=2.19.1=h0c24ade_1 - - ld_impl_linux-64=2.46.1=default_hbd61a6d_102 + - ld_impl_linux-64=2.45.1=default_hbd61a6d_102 - lerc=4.1.0=hdb68285_0 - libaec=1.1.5=h088129d_0 - libblas=3.11.0=6_h5875eb1_mkl @@ -173,7 +173,7 @@ dependencies: - mdit-py-plugins=0.6.1=pyhd8ed1ab_0 - mdurl=0.1.2=pyhd8ed1ab_1 - metis=5.1.0=hd0bcaf9_1007 - - mistune=3.3.4=pyhcf101f3_0 + - mistune=3.3.3=pyhcf101f3_0 - mkl=2025.3.1=h0e700b2_12 - msgpack-python=1.2.1=py312h0a2e395_1 - mumps-include=5.8.2=h5a610fb_2 @@ -187,7 +187,7 @@ dependencies: - ncurses=6.6=hdb14827_0 - nest-asyncio2=1.7.2=pyhcf101f3_0 - nodejs=22.6.0=hc19f0b3_1 - - notebook=7.6.1=pyhcf101f3_0 + - notebook=7.6.0=pyhcf101f3_0 - notebook-shim=0.2.4=pyhd8ed1ab_1 - numcodecs=0.15.1=py312hf79963d_1 - numpy=2.4.6=py312h33ff503_0 @@ -205,7 +205,7 @@ dependencies: - pexpect=4.9.0=pyhd8ed1ab_1 - pillow=12.3.0=py312h50c33e8_0 - pip=26.1.2=pyh8b19718_0 - - platformdirs=4.11.0=pyhcf101f3_0 + - platformdirs=4.10.0=pyhcf101f3_0 - pluggy=1.6.0=pyhf9edf01_1 - prometheus_client=0.25.0=pyhd8ed1ab_0 - prompt-toolkit=3.0.52=pyha770c72_0 @@ -254,7 +254,7 @@ dependencies: - sniffio=1.3.1=pyhd8ed1ab_2 - snowballstemmer=3.1.1=pyhd8ed1ab_0 - sortedcontainers=2.4.0=pyhd8ed1ab_1 - - soupsieve=2.9.1=pyhd8ed1ab_0 + - soupsieve=2.8.4=pyhd8ed1ab_0 - sphinx=5.3.0=pyhd8ed1ab_0 - sphinxcontrib-applehelp=2.0.0=pyhd8ed1ab_1 - sphinxcontrib-devhelp=2.0.0=pyhd8ed1ab_1 @@ -268,19 +268,19 @@ dependencies: - terminado=0.18.1=pyhc90fa1f_1 - threadpoolctl=3.6.0=pyhecae5ae_0 - tinycss2=1.4.0=pyhd8ed1ab_0 - - tk=8.6.13=noxft_hd70dff1_3 + - tk=8.6.13=noxft_h366c992_103 - tomli=2.4.1=pyhcf101f3_0 - - tomlkit=0.15.1=pyhcf101f3_0 + - tomlkit=0.15.0=pyha770c72_0 - toolz=1.1.0=pyhd8ed1ab_1 - tornado=6.5.7=py312h4c3975b_0 - - tqdm=4.69.0=pyh8f84b5b_0 + - tqdm=4.68.4=pyh8f84b5b_0 - traitlets=5.15.1=pyhcf101f3_0 - trimesh=4.12.2=pyh7b2049a_0 - typing-extensions=4.16.0=h69aa097_0 - typing-inspection=0.4.2=pyhcf101f3_2 - typing_extensions=4.16.0=pyhcf101f3_0 - typing_utils=0.1.0=pyhd8ed1ab_1 - - tzdata=2026c=h151e31d_0 + - tzdata=2025c=hc9c84f9_1 - unicodedata2=17.0.1=py312h4c3975b_0 - uri-template=1.3.0=pyhd8ed1ab_1 - urllib3=2.7.0=pyhd8ed1ab_0 @@ -303,10 +303,10 @@ dependencies: - zlib-ng=2.3.3=hceb46e0_1 - zstd=1.5.7=hb78ec9c_6 - pip: - - geoapps-utils == 0.7.0 --hash=sha256:97796a9a1dbaed392eb2be97a1e66f2ae894b64460bdb940f42a945e31e2c417 - - geoh5py == 0.13.1rc2 --hash=sha256:3a01ce48994bad4bd2ee0f7f97eb8bdf6bba490b11d9cbbaac12f1a0f300e389 - - grid-apps == 0.2.0 --hash=sha256:77dd1881c3467687f7b1698eefc285368f0f84a1b154651e97006b36a668fa54 - - mira-simpeg == 0.25.0.1 --hash=sha256:8bceaaf017df95ea925e20feaa2e6a5dc0f8fede434738d6493c775cb5341b38 + - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@2deafccc38a40039dc6b3f376bf0e3c9d0371498 + - geoh5py @ git+https://github.com/MiraGeoscience/geoh5py.git@3722011aa9fd8512f1bd6a3e58e2220a88ca8ce2 + - grid-apps @ git+https://github.com/MiraGeoscience/grid-apps.git@7bc33d170f03dd6da15ee531b49cdb5835b36ea4 + - mira-simpeg @ git+https://github.com/MiraGeoscience/simpeg.git@87230755b40f3961fd9b2383df191c140a29c601 variables: KMP_WARNINGS: 0 diff --git a/environments/py-3.12-linux-64.conda.lock.yml b/environments/py-3.12-linux-64.conda.lock.yml index 5ffe32b43..fd0271c3c 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: 8ffe2b7bd402c394a70b91fad1304325a8ee68470e2f1565a7a098270cee1b60 +# input_hash: d4e89447ddc89490b9baa6153c503886506d9dbc1b81466f4cc3e6a28d370325 channels: - conda-forge @@ -9,22 +9,22 @@ dependencies: - _openmp_mutex=4.5=7_kmp_llvm - annotated-types=0.7.0=pyhd8ed1ab_1 - asciitree=0.3.3=py_2 - - aws-c-auth=0.10.4=hb7a77c6_1 - - aws-c-cal=0.9.14=h2aa3ae6_4 - - aws-c-common=0.14.2=hb03c661_0 - - aws-c-compression=0.3.2=h720e601_4 - - aws-c-http=0.11.0=h38ae05a_4 - - aws-c-io=0.27.3=h6f4d18d_1 - - aws-c-s3=0.12.8=h46fcd08_1 - - aws-c-sdkutils=0.2.7=h720e601_2 - - aws-checksums=0.2.10=h720e601_4 + - aws-c-auth=0.10.3=hdc83ae9_4 + - aws-c-cal=0.9.14=h6e18c7b_3 + - aws-c-common=0.14.1=hb03c661_0 + - aws-c-compression=0.3.2=haaf8e00_3 + - aws-c-http=0.11.0=h434eaf2_3 + - aws-c-io=0.26.3=ha0b0e19_6 + - aws-c-s3=0.12.6=hd767fb6_1 + - aws-c-sdkutils=0.2.7=haaf8e00_1 + - aws-checksums=0.2.10=haaf8e00_3 - backports.zstd=1.6.0=py312h90b7ffd_0 - bokeh=3.6.3=pyhd8ed1ab_0 - brotli=1.2.0=hed03a55_1 - brotli-bin=1.2.0=hb03c661_1 - brotli-python=1.2.0=py312hdb49522_1 - bzip2=1.0.8=hda65f42_9 - - c-ares=1.34.8=hb03c661_0 + - c-ares=1.34.6=hb03c661_0 - ca-certificates=2026.6.17=hbd8a1cb_0 - cached-property=1.5.2=hd8ed1ab_2 - cached_property=1.5.2=pyha770c72_2 @@ -45,7 +45,7 @@ dependencies: - geoana=0.8.1=np2py312h2a48985_0 - h2=4.3.0=pyhcf101f3_0 - h5py=3.16.0=nompi_py312ha829cd9_102 - - hdf5=2.1.0=nompi_h654f344_110 + - hdf5=2.1.0=nompi_hc8ae080_109 - hpack=4.2.0=pyhd8ed1ab_0 - hyperframe=6.1.0=pyhd8ed1ab_0 - icu=75.1=he02047a_0 @@ -56,7 +56,7 @@ dependencies: - kiwisolver=1.5.0=py312h0a2e395_0 - krb5=1.22.2=hbde042b_1 - lcms2=2.19.1=h0c24ade_1 - - ld_impl_linux-64=2.46.1=default_hbd61a6d_102 + - ld_impl_linux-64=2.45.1=default_hbd61a6d_102 - lerc=4.1.0=hdb68285_0 - libaec=1.1.5=h088129d_0 - libblas=3.11.0=6_h5875eb1_mkl @@ -148,15 +148,15 @@ dependencies: - tbb=2023.0.0=hab88423_2 - tblib=3.2.2=pyhcf101f3_0 - threadpoolctl=3.6.0=pyhecae5ae_0 - - tk=8.6.13=noxft_hd70dff1_3 + - tk=8.6.13=noxft_h366c992_103 - toolz=1.1.0=pyhd8ed1ab_1 - tornado=6.5.7=py312h4c3975b_0 - - tqdm=4.69.0=pyh8f84b5b_0 + - tqdm=4.68.4=pyh8f84b5b_0 - trimesh=4.12.2=pyh7b2049a_0 - typing-extensions=4.16.0=h69aa097_0 - typing-inspection=0.4.2=pyhcf101f3_2 - typing_extensions=4.16.0=pyhcf101f3_0 - - tzdata=2026c=h151e31d_0 + - tzdata=2025c=hc9c84f9_1 - unicodedata2=17.0.1=py312h4c3975b_0 - urllib3=2.7.0=pyhd8ed1ab_0 - wheel=0.47.0=pyhd8ed1ab_0 @@ -171,10 +171,10 @@ dependencies: - zlib-ng=2.3.3=hceb46e0_1 - zstd=1.5.7=hb78ec9c_6 - pip: - - geoapps-utils == 0.7.0 --hash=sha256:97796a9a1dbaed392eb2be97a1e66f2ae894b64460bdb940f42a945e31e2c417 - - geoh5py == 0.13.1rc2 --hash=sha256:3a01ce48994bad4bd2ee0f7f97eb8bdf6bba490b11d9cbbaac12f1a0f300e389 - - grid-apps == 0.2.0 --hash=sha256:77dd1881c3467687f7b1698eefc285368f0f84a1b154651e97006b36a668fa54 - - mira-simpeg == 0.25.0.1 --hash=sha256:8bceaaf017df95ea925e20feaa2e6a5dc0f8fede434738d6493c775cb5341b38 + - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@2deafccc38a40039dc6b3f376bf0e3c9d0371498 + - geoh5py @ git+https://github.com/MiraGeoscience/geoh5py.git@3722011aa9fd8512f1bd6a3e58e2220a88ca8ce2 + - grid-apps @ git+https://github.com/MiraGeoscience/grid-apps.git@7bc33d170f03dd6da15ee531b49cdb5835b36ea4 + - mira-simpeg @ git+https://github.com/MiraGeoscience/simpeg.git@87230755b40f3961fd9b2383df191c140a29c601 variables: KMP_WARNINGS: 0 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 fb09b0e53..27bcc49ef 100644 --- a/environments/py-3.12-win-64-dev.conda.lock.yml +++ b/environments/py-3.12-win-64-dev.conda.lock.yml @@ -1,13 +1,13 @@ # Generated by conda-lock. # platform: win-64 -# input_hash: 6e2adf071404327a6402729138200c74fea182b48456560110dcf57a2bfb8744 +# input_hash: b09e66847f78fcc9809b6d746d19563387f7cb275b1d47dddaf7b7a70cff0686 channels: - conda-forge - nodefaults dependencies: - _openmp_mutex=4.5=20_gnu - - _python_abi3_support=1.0=hd8ed1ab_3 + - _python_abi3_support=1.0=hd8ed1ab_2 - alabaster=0.7.16=pyhd8ed1ab_0 - annotated-types=0.7.0=pyhd8ed1ab_1 - anyio=4.14.2=pyhcf101f3_0 @@ -16,18 +16,18 @@ dependencies: - arrow=1.4.0=pyhcf101f3_0 - asciitree=0.3.3=py_2 - astroid=4.0.4=py312h2e8e312_0 - - asttokens=3.0.2=pyhd8ed1ab_0 + - asttokens=3.0.1=pyhd8ed1ab_0 - async-lru=2.3.0=pyhcf101f3_0 - attrs=26.1.0=pyhcf101f3_0 - - aws-c-auth=0.10.4=hc6e9107_1 - - aws-c-cal=0.9.14=h032d170_4 - - aws-c-common=0.14.2=hfd05255_0 - - aws-c-compression=0.3.2=h1da161f_4 - - aws-c-http=0.11.0=h667fc28_4 - - aws-c-io=0.27.3=hbdc738f_1 - - aws-c-s3=0.12.8=hd7ee1a1_1 - - aws-c-sdkutils=0.2.7=h1da161f_2 - - aws-checksums=0.2.10=h1da161f_4 + - aws-c-auth=0.10.3=h0a1c2aa_4 + - aws-c-cal=0.9.14=h92e57be_3 + - aws-c-common=0.14.1=hfd05255_0 + - aws-c-compression=0.3.2=h4c63076_3 + - aws-c-http=0.11.0=h2c07012_3 + - aws-c-io=0.26.3=hf70332f_6 + - aws-c-s3=0.12.6=hcb9225d_1 + - aws-c-sdkutils=0.2.7=h4c63076_1 + - aws-checksums=0.2.10=h4c63076_3 - babel=2.18.0=pyhcf101f3_1 - backports.zstd=1.6.0=py312h06d0912_0 - beautifulsoup4=4.15.0=pyha770c72_0 @@ -41,7 +41,7 @@ dependencies: - ca-certificates=2026.6.17=h4c7d964_0 - cached-property=1.5.2=hd8ed1ab_2 - cached_property=1.5.2=pyha770c72_2 - - certifi=2026.7.22=pyhd8ed1ab_0 + - certifi=2026.6.17=pyhd8ed1ab_0 - cffi=2.1.0=py312he06e257_0 - charset-normalizer=3.4.9=pyhd8ed1ab_0 - click=8.4.2=pyh6dadd2b_0 @@ -49,7 +49,7 @@ dependencies: - colorama=0.4.6=pyhd8ed1ab_1 - comm=0.2.3=pyhe01879c_0 - contourpy=1.3.3=py312h78d62e6_4 - - coverage=7.15.2=py312h05f76fc_0 + - coverage=7.15.0=py312h05f76fc_0 - cpython=3.12.13=py312hd8ed1ab_0 - cycler=0.12.1=pyhcf101f3_2 - cytoolz=1.1.0=py312he06e257_2 @@ -74,12 +74,12 @@ dependencies: - h11=0.16.0=pyhcf101f3_1 - h2=4.3.0=pyhcf101f3_0 - h5py=3.16.0=nompi_py312h5ddec8c_102 - - hdf5=2.1.0=nompi_h0d3e4b2_110 + - hdf5=2.1.0=nompi_h0065017_109 - hpack=4.2.0=pyhd8ed1ab_0 - httpcore=1.0.9=pyh29332c3_0 - httpx=0.28.1=pyhd8ed1ab_0 - hyperframe=6.1.0=pyhd8ed1ab_0 - - icu=78.3=h637d24d_1 + - icu=78.3=h637d24d_0 - idna=3.18=pyhcf101f3_0 - imagesize=2.0.0=pyhd8ed1ab_0 - importlib-metadata=9.0.0=pyhcf101f3_0 @@ -100,18 +100,18 @@ dependencies: - jsonschema-specifications=2025.9.1=pyhcf101f3_0 - jsonschema-with-format-nongpl=4.26.0=hcf101f3_0 - jupyter-book=2.1.6=pyhcf101f3_0 - - jupyter-builder=1.1.1=pyhcf101f3_0 + - jupyter-builder=1.1.0=pyhcf101f3_0 - jupyter-lsp=2.3.1=pyhcf101f3_0 - jupyter_client=8.9.1=pyhcf101f3_0 - jupyter_core=5.9.1=pyh6dadd2b_0 - jupyter_events=0.12.1=pyhcf101f3_0 - jupyter_server=2.20.0=pyhcf101f3_0 - jupyter_server_terminals=0.5.4=pyhcf101f3_0 - - jupyterlab=4.6.2=pyhd8ed1ab_0 + - jupyterlab=4.6.1=pyhd8ed1ab_0 - jupyterlab_pygments=0.3.0=pyhd8ed1ab_2 - jupyterlab_server=2.28.0=pyhcf101f3_0 - jupyterlab_widgets=1.1.11=pyhd8ed1ab_0 - - jupytext=1.19.5=pyh0398c0e_0 + - jupytext=1.19.4=pyh0398c0e_0 - kiwisolver=1.5.0=py312h78d62e6_0 - krb5=1.22.2=h719d79b_1 - lark=1.3.1=pyhd8ed1ab_0 @@ -159,7 +159,7 @@ dependencies: - mccabe=0.7.0=pyhd8ed1ab_1 - mdit-py-plugins=0.6.1=pyhd8ed1ab_0 - mdurl=0.1.2=pyhd8ed1ab_1 - - mistune=3.3.4=pyhcf101f3_0 + - mistune=3.3.3=pyhcf101f3_0 - mkl=2025.3.1=hac47afa_13 - msgpack-python=1.2.1=py312h78d62e6_1 - mumps-seq=5.8.2=h607cc0b_2 @@ -171,7 +171,7 @@ dependencies: - nbformat=5.10.4=pyhd8ed1ab_1 - nest-asyncio2=1.7.2=pyhcf101f3_0 - nodejs=26.5.0=h80d1838_0 - - notebook=7.6.1=pyhcf101f3_0 + - notebook=7.6.0=pyhcf101f3_0 - notebook-shim=0.2.4=pyhd8ed1ab_1 - numcodecs=0.15.1=py312hc128f0a_1 - numpy=2.4.6=py312ha3f287d_0 @@ -188,7 +188,7 @@ dependencies: - partd=1.4.2=pyhd8ed1ab_0 - pillow=12.3.0=py312h31f0997_0 - pip=26.1.2=pyh8b19718_0 - - platformdirs=4.11.0=pyhcf101f3_0 + - platformdirs=4.10.0=pyhcf101f3_0 - pluggy=1.6.0=pyhf9edf01_1 - prometheus_client=0.25.0=pyhd8ed1ab_0 - prompt-toolkit=3.0.52=pyha770c72_0 @@ -236,7 +236,7 @@ dependencies: - sniffio=1.3.1=pyhd8ed1ab_2 - snowballstemmer=3.1.1=pyhd8ed1ab_0 - sortedcontainers=2.4.0=pyhd8ed1ab_1 - - soupsieve=2.9.1=pyhd8ed1ab_0 + - soupsieve=2.8.4=pyhd8ed1ab_0 - sphinx=5.3.0=pyhd8ed1ab_0 - sphinxcontrib-applehelp=2.0.0=pyhd8ed1ab_1 - sphinxcontrib-devhelp=2.0.0=pyhd8ed1ab_1 @@ -250,19 +250,19 @@ dependencies: - terminado=0.18.1=pyh6dadd2b_1 - threadpoolctl=3.6.0=pyhecae5ae_0 - tinycss2=1.4.0=pyhd8ed1ab_0 - - tk=8.6.13=h967ab96_3 + - tk=8.6.13=h6ed50ae_3 - tomli=2.4.1=pyhcf101f3_0 - - tomlkit=0.15.1=pyhcf101f3_0 + - tomlkit=0.15.0=pyha770c72_0 - toolz=1.1.0=pyhd8ed1ab_1 - tornado=6.5.7=py312he06e257_0 - - tqdm=4.69.0=pyha7b4d00_0 + - tqdm=4.68.4=pyha7b4d00_0 - traitlets=5.15.1=pyhcf101f3_0 - trimesh=4.12.2=pyh7b2049a_0 - typing-extensions=4.16.0=h69aa097_0 - typing-inspection=0.4.2=pyhcf101f3_2 - typing_extensions=4.16.0=pyhcf101f3_0 - typing_utils=0.1.0=pyhd8ed1ab_1 - - tzdata=2026c=h151e31d_0 + - tzdata=2025c=hc9c84f9_1 - ucrt=10.0.26100.0=h57928b3_0 - unicodedata2=17.0.1=py312he06e257_0 - uri-template=1.3.0=pyhd8ed1ab_1 @@ -291,10 +291,10 @@ dependencies: - zlib-ng=2.3.3=h0261ad2_1 - zstd=1.5.7=h534d264_6 - pip: - - geoapps-utils == 0.7.0 --hash=sha256:97796a9a1dbaed392eb2be97a1e66f2ae894b64460bdb940f42a945e31e2c417 - - geoh5py == 0.13.1rc2 --hash=sha256:3a01ce48994bad4bd2ee0f7f97eb8bdf6bba490b11d9cbbaac12f1a0f300e389 - - grid-apps == 0.2.0 --hash=sha256:77dd1881c3467687f7b1698eefc285368f0f84a1b154651e97006b36a668fa54 - - mira-simpeg == 0.25.0.1 --hash=sha256:8bceaaf017df95ea925e20feaa2e6a5dc0f8fede434738d6493c775cb5341b38 + - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@2deafccc38a40039dc6b3f376bf0e3c9d0371498 + - geoh5py @ git+https://github.com/MiraGeoscience/geoh5py.git@3722011aa9fd8512f1bd6a3e58e2220a88ca8ce2 + - grid-apps @ git+https://github.com/MiraGeoscience/grid-apps.git@7bc33d170f03dd6da15ee531b49cdb5835b36ea4 + - mira-simpeg @ git+https://github.com/MiraGeoscience/simpeg.git@87230755b40f3961fd9b2383df191c140a29c601 variables: KMP_WARNINGS: 0 diff --git a/environments/py-3.12-win-64.conda.lock.yml b/environments/py-3.12-win-64.conda.lock.yml index 2c69d1ee7..0ba44131a 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: 6e2adf071404327a6402729138200c74fea182b48456560110dcf57a2bfb8744 +# input_hash: b09e66847f78fcc9809b6d746d19563387f7cb275b1d47dddaf7b7a70cff0686 channels: - conda-forge @@ -9,15 +9,15 @@ dependencies: - _openmp_mutex=4.5=20_gnu - annotated-types=0.7.0=pyhd8ed1ab_1 - asciitree=0.3.3=py_2 - - aws-c-auth=0.10.4=hc6e9107_1 - - aws-c-cal=0.9.14=h032d170_4 - - aws-c-common=0.14.2=hfd05255_0 - - aws-c-compression=0.3.2=h1da161f_4 - - aws-c-http=0.11.0=h667fc28_4 - - aws-c-io=0.27.3=hbdc738f_1 - - aws-c-s3=0.12.8=hd7ee1a1_1 - - aws-c-sdkutils=0.2.7=h1da161f_2 - - aws-checksums=0.2.10=h1da161f_4 + - aws-c-auth=0.10.3=h0a1c2aa_4 + - aws-c-cal=0.9.14=h92e57be_3 + - aws-c-common=0.14.1=hfd05255_0 + - aws-c-compression=0.3.2=h4c63076_3 + - aws-c-http=0.11.0=h2c07012_3 + - aws-c-io=0.26.3=hf70332f_6 + - aws-c-s3=0.12.6=hcb9225d_1 + - aws-c-sdkutils=0.2.7=h4c63076_1 + - aws-checksums=0.2.10=h4c63076_3 - backports.zstd=1.6.0=py312h06d0912_0 - bokeh=3.6.3=pyhd8ed1ab_0 - brotli=1.2.0=h2d644bc_1 @@ -45,10 +45,10 @@ dependencies: - geoana=0.8.1=np2py312h7c90ba1_0 - h2=4.3.0=pyhcf101f3_0 - h5py=3.16.0=nompi_py312h5ddec8c_102 - - hdf5=2.1.0=nompi_h0d3e4b2_110 + - hdf5=2.1.0=nompi_h0065017_109 - hpack=4.2.0=pyhd8ed1ab_0 - hyperframe=6.1.0=pyhd8ed1ab_0 - - icu=78.3=h637d24d_1 + - icu=78.3=h637d24d_0 - importlib-metadata=9.0.0=pyhcf101f3_0 - jinja2=3.1.6=pyhcf101f3_1 - joblib=1.5.3=pyhd8ed1ab_0 @@ -132,15 +132,15 @@ dependencies: - tbb=2023.0.0=hd3d4ead_2 - tblib=3.2.2=pyhcf101f3_0 - threadpoolctl=3.6.0=pyhecae5ae_0 - - tk=8.6.13=h967ab96_3 + - tk=8.6.13=h6ed50ae_3 - toolz=1.1.0=pyhd8ed1ab_1 - tornado=6.5.7=py312he06e257_0 - - tqdm=4.69.0=pyha7b4d00_0 + - tqdm=4.68.4=pyha7b4d00_0 - trimesh=4.12.2=pyh7b2049a_0 - typing-extensions=4.16.0=h69aa097_0 - typing-inspection=0.4.2=pyhcf101f3_2 - typing_extensions=4.16.0=pyhcf101f3_0 - - tzdata=2026c=h151e31d_0 + - tzdata=2025c=hc9c84f9_1 - ucrt=10.0.26100.0=h57928b3_0 - unicodedata2=17.0.1=py312he06e257_0 - urllib3=2.7.0=pyhd8ed1ab_0 @@ -161,10 +161,10 @@ dependencies: - zlib-ng=2.3.3=h0261ad2_1 - zstd=1.5.7=h534d264_6 - pip: - - geoapps-utils == 0.7.0 --hash=sha256:97796a9a1dbaed392eb2be97a1e66f2ae894b64460bdb940f42a945e31e2c417 - - geoh5py == 0.13.1rc2 --hash=sha256:3a01ce48994bad4bd2ee0f7f97eb8bdf6bba490b11d9cbbaac12f1a0f300e389 - - grid-apps == 0.2.0 --hash=sha256:77dd1881c3467687f7b1698eefc285368f0f84a1b154651e97006b36a668fa54 - - mira-simpeg == 0.25.0.1 --hash=sha256:8bceaaf017df95ea925e20feaa2e6a5dc0f8fede434738d6493c775cb5341b38 + - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@2deafccc38a40039dc6b3f376bf0e3c9d0371498 + - geoh5py @ git+https://github.com/MiraGeoscience/geoh5py.git@3722011aa9fd8512f1bd6a3e58e2220a88ca8ce2 + - grid-apps @ git+https://github.com/MiraGeoscience/grid-apps.git@7bc33d170f03dd6da15ee531b49cdb5835b36ea4 + - mira-simpeg @ git+https://github.com/MiraGeoscience/simpeg.git@87230755b40f3961fd9b2383df191c140a29c601 variables: KMP_WARNINGS: 0 diff --git a/environments/py-3.13-linux-64-dev.conda.lock.yml b/environments/py-3.13-linux-64-dev.conda.lock.yml index c21b35854..0ea4fcddf 100644 --- a/environments/py-3.13-linux-64-dev.conda.lock.yml +++ b/environments/py-3.13-linux-64-dev.conda.lock.yml @@ -1,13 +1,13 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: e47fe9019cd75c24e0ceebabf988abf3e89f41557900def026b5fa36554bf11d +# input_hash: 46e87a977a45c0b272bd0321b439febe2f11e1e69a0ddc4e780729ddb9a22d9c channels: - conda-forge - nodefaults dependencies: - _openmp_mutex=4.5=7_kmp_llvm - - _python_abi3_support=1.0=hd8ed1ab_3 + - _python_abi3_support=1.0=hd8ed1ab_2 - alabaster=0.7.16=pyhd8ed1ab_0 - annotated-types=0.7.0=pyhd8ed1ab_1 - anyio=4.14.2=pyhcf101f3_0 @@ -16,18 +16,18 @@ dependencies: - arrow=1.4.0=pyhcf101f3_0 - asciitree=0.3.3=py_2 - astroid=4.0.4=py313h78bf25f_0 - - asttokens=3.0.2=pyhd8ed1ab_0 + - asttokens=3.0.1=pyhd8ed1ab_0 - async-lru=2.3.0=pyhcf101f3_0 - attrs=26.1.0=pyhcf101f3_0 - - aws-c-auth=0.10.4=hb7a77c6_1 - - aws-c-cal=0.9.14=h2aa3ae6_4 - - aws-c-common=0.14.2=hb03c661_0 - - aws-c-compression=0.3.2=h720e601_4 - - aws-c-http=0.11.0=h38ae05a_4 - - aws-c-io=0.27.3=h6f4d18d_1 - - aws-c-s3=0.12.8=h46fcd08_1 - - aws-c-sdkutils=0.2.7=h720e601_2 - - aws-checksums=0.2.10=h720e601_4 + - aws-c-auth=0.10.3=hdc83ae9_4 + - aws-c-cal=0.9.14=h6e18c7b_3 + - aws-c-common=0.14.1=hb03c661_0 + - aws-c-compression=0.3.2=haaf8e00_3 + - aws-c-http=0.11.0=h434eaf2_3 + - aws-c-io=0.26.3=ha0b0e19_6 + - aws-c-s3=0.12.6=hd767fb6_1 + - aws-c-sdkutils=0.2.7=haaf8e00_1 + - aws-checksums=0.2.10=haaf8e00_3 - babel=2.18.0=pyhcf101f3_1 - backports.zstd=1.6.0=py313h18e8e13_0 - beautifulsoup4=4.15.0=pyha770c72_0 @@ -38,11 +38,11 @@ dependencies: - brotli-bin=1.2.0=hb03c661_1 - brotli-python=1.2.0=py313hf159716_1 - bzip2=1.0.8=hda65f42_9 - - c-ares=1.34.8=hb03c661_0 + - c-ares=1.34.6=hb03c661_0 - ca-certificates=2026.6.17=hbd8a1cb_0 - cached-property=1.5.2=hd8ed1ab_2 - cached_property=1.5.2=pyha770c72_2 - - certifi=2026.7.22=pyhd8ed1ab_0 + - certifi=2026.6.17=pyhd8ed1ab_0 - cffi=2.1.0=py313hf46b229_0 - charset-normalizer=3.4.9=pyhd8ed1ab_0 - click=8.4.2=pyhc90fa1f_0 @@ -50,7 +50,7 @@ dependencies: - colorama=0.4.6=pyhd8ed1ab_1 - comm=0.2.3=pyhe01879c_0 - contourpy=1.3.3=py313hc8edb43_4 - - coverage=7.15.2=py313h3dea7bd_0 + - coverage=7.15.0=py313h3dea7bd_0 - cpython=3.13.14=py313hd8ed1ab_100 - cycler=0.12.1=pyhcf101f3_2 - cytoolz=1.1.0=py313h07c4f96_2 @@ -75,7 +75,7 @@ dependencies: - h11=0.16.0=pyhcf101f3_1 - h2=4.3.0=pyhcf101f3_0 - h5py=3.16.0=nompi_py313h22c32d4_102 - - hdf5=2.1.0=nompi_h654f344_110 + - hdf5=2.1.0=nompi_hc8ae080_109 - hpack=4.2.0=pyhd8ed1ab_0 - httpcore=1.0.9=pyh29332c3_0 - httpx=0.28.1=pyhd8ed1ab_0 @@ -101,24 +101,24 @@ dependencies: - jsonschema-specifications=2025.9.1=pyhcf101f3_0 - jsonschema-with-format-nongpl=4.26.0=hcf101f3_0 - jupyter-book=2.1.6=pyhcf101f3_0 - - jupyter-builder=1.1.1=pyhcf101f3_0 + - jupyter-builder=1.1.0=pyhcf101f3_0 - jupyter-lsp=2.3.1=pyhcf101f3_0 - jupyter_client=8.9.1=pyhcf101f3_0 - jupyter_core=5.9.1=pyhc90fa1f_0 - jupyter_events=0.12.1=pyhcf101f3_0 - jupyter_server=2.20.0=pyhcf101f3_0 - jupyter_server_terminals=0.5.4=pyhcf101f3_0 - - jupyterlab=4.6.2=pyhd8ed1ab_0 + - jupyterlab=4.6.1=pyhd8ed1ab_0 - jupyterlab_pygments=0.3.0=pyhd8ed1ab_2 - jupyterlab_server=2.28.0=pyhcf101f3_0 - jupyterlab_widgets=1.1.11=pyhd8ed1ab_0 - - jupytext=1.19.5=pyh0398c0e_0 + - jupytext=1.19.4=pyh0398c0e_0 - keyutils=1.6.3=hb9d3cd8_0 - kiwisolver=1.5.0=py313hc8edb43_0 - krb5=1.22.2=hbde042b_1 - lark=1.3.1=pyhd8ed1ab_0 - lcms2=2.19.1=h0c24ade_1 - - ld_impl_linux-64=2.46.1=default_hbd61a6d_102 + - ld_impl_linux-64=2.45.1=default_hbd61a6d_102 - lerc=4.1.0=hdb68285_0 - libaec=1.1.5=h088129d_0 - libblas=3.11.0=6_h5875eb1_mkl @@ -172,7 +172,7 @@ dependencies: - mdit-py-plugins=0.6.1=pyhd8ed1ab_0 - mdurl=0.1.2=pyhd8ed1ab_1 - metis=5.1.0=hd0bcaf9_1007 - - mistune=3.3.4=pyhcf101f3_0 + - mistune=3.3.3=pyhcf101f3_0 - mkl=2025.3.1=h0e700b2_12 - msgpack-python=1.2.1=py313hc8edb43_1 - mumps-include=5.8.2=h5a610fb_2 @@ -186,7 +186,7 @@ dependencies: - ncurses=6.6=hdb14827_0 - nest-asyncio2=1.7.2=pyhcf101f3_0 - nodejs=22.6.0=hc19f0b3_1 - - notebook=7.6.1=pyhcf101f3_0 + - notebook=7.6.0=pyhcf101f3_0 - notebook-shim=0.2.4=pyhd8ed1ab_1 - numcodecs=0.15.1=py313h08cd8bf_1 - numpy=2.4.6=py313hf6604e3_0 @@ -204,7 +204,7 @@ dependencies: - pexpect=4.9.0=pyhd8ed1ab_1 - pillow=12.3.0=py313h80991f8_0 - pip=26.1.2=pyh145f28c_0 - - platformdirs=4.11.0=pyhcf101f3_0 + - platformdirs=4.10.0=pyhcf101f3_0 - pluggy=1.6.0=pyhf9edf01_1 - prometheus_client=0.25.0=pyhd8ed1ab_0 - prompt-toolkit=3.0.52=pyha770c72_0 @@ -253,7 +253,7 @@ dependencies: - sniffio=1.3.1=pyhd8ed1ab_2 - snowballstemmer=3.1.1=pyhd8ed1ab_0 - sortedcontainers=2.4.0=pyhd8ed1ab_1 - - soupsieve=2.9.1=pyhd8ed1ab_0 + - soupsieve=2.8.4=pyhd8ed1ab_0 - sphinx=5.3.0=pyhd8ed1ab_0 - sphinxcontrib-applehelp=2.0.0=pyhd8ed1ab_1 - sphinxcontrib-devhelp=2.0.0=pyhd8ed1ab_1 @@ -267,19 +267,19 @@ dependencies: - terminado=0.18.1=pyhc90fa1f_1 - threadpoolctl=3.6.0=pyhecae5ae_0 - tinycss2=1.4.0=pyhd8ed1ab_0 - - tk=8.6.13=noxft_hd70dff1_3 + - tk=8.6.13=noxft_h366c992_103 - tomli=2.4.1=pyhcf101f3_0 - - tomlkit=0.15.1=pyhcf101f3_0 + - tomlkit=0.15.0=pyha770c72_0 - toolz=1.1.0=pyhd8ed1ab_1 - tornado=6.5.7=py313h07c4f96_0 - - tqdm=4.69.0=pyh8f84b5b_0 + - tqdm=4.68.4=pyh8f84b5b_0 - traitlets=5.15.1=pyhcf101f3_0 - trimesh=4.12.2=pyh7b2049a_0 - typing-extensions=4.16.0=h69aa097_0 - typing-inspection=0.4.2=pyhcf101f3_2 - typing_extensions=4.16.0=pyhcf101f3_0 - typing_utils=0.1.0=pyhd8ed1ab_1 - - tzdata=2026c=h151e31d_0 + - tzdata=2025c=hc9c84f9_1 - uri-template=1.3.0=pyhd8ed1ab_1 - urllib3=2.7.0=pyhd8ed1ab_0 - wcwidth=0.8.2=pyhd8ed1ab_0 @@ -300,10 +300,10 @@ dependencies: - zlib-ng=2.3.3=hceb46e0_1 - zstd=1.5.7=hb78ec9c_6 - pip: - - geoapps-utils == 0.7.0 --hash=sha256:97796a9a1dbaed392eb2be97a1e66f2ae894b64460bdb940f42a945e31e2c417 - - geoh5py == 0.13.1rc2 --hash=sha256:3a01ce48994bad4bd2ee0f7f97eb8bdf6bba490b11d9cbbaac12f1a0f300e389 - - grid-apps == 0.2.0 --hash=sha256:77dd1881c3467687f7b1698eefc285368f0f84a1b154651e97006b36a668fa54 - - mira-simpeg == 0.25.0.1 --hash=sha256:8bceaaf017df95ea925e20feaa2e6a5dc0f8fede434738d6493c775cb5341b38 + - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@2deafccc38a40039dc6b3f376bf0e3c9d0371498 + - geoh5py @ git+https://github.com/MiraGeoscience/geoh5py.git@3722011aa9fd8512f1bd6a3e58e2220a88ca8ce2 + - grid-apps @ git+https://github.com/MiraGeoscience/grid-apps.git@7bc33d170f03dd6da15ee531b49cdb5835b36ea4 + - mira-simpeg @ git+https://github.com/MiraGeoscience/simpeg.git@87230755b40f3961fd9b2383df191c140a29c601 variables: KMP_WARNINGS: 0 diff --git a/environments/py-3.13-linux-64.conda.lock.yml b/environments/py-3.13-linux-64.conda.lock.yml index f4f51f35f..601dae85d 100644 --- a/environments/py-3.13-linux-64.conda.lock.yml +++ b/environments/py-3.13-linux-64.conda.lock.yml @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: e47fe9019cd75c24e0ceebabf988abf3e89f41557900def026b5fa36554bf11d +# input_hash: 46e87a977a45c0b272bd0321b439febe2f11e1e69a0ddc4e780729ddb9a22d9c channels: - conda-forge @@ -9,22 +9,22 @@ dependencies: - _openmp_mutex=4.5=7_kmp_llvm - annotated-types=0.7.0=pyhd8ed1ab_1 - asciitree=0.3.3=py_2 - - aws-c-auth=0.10.4=hb7a77c6_1 - - aws-c-cal=0.9.14=h2aa3ae6_4 - - aws-c-common=0.14.2=hb03c661_0 - - aws-c-compression=0.3.2=h720e601_4 - - aws-c-http=0.11.0=h38ae05a_4 - - aws-c-io=0.27.3=h6f4d18d_1 - - aws-c-s3=0.12.8=h46fcd08_1 - - aws-c-sdkutils=0.2.7=h720e601_2 - - aws-checksums=0.2.10=h720e601_4 + - aws-c-auth=0.10.3=hdc83ae9_4 + - aws-c-cal=0.9.14=h6e18c7b_3 + - aws-c-common=0.14.1=hb03c661_0 + - aws-c-compression=0.3.2=haaf8e00_3 + - aws-c-http=0.11.0=h434eaf2_3 + - aws-c-io=0.26.3=ha0b0e19_6 + - aws-c-s3=0.12.6=hd767fb6_1 + - aws-c-sdkutils=0.2.7=haaf8e00_1 + - aws-checksums=0.2.10=haaf8e00_3 - backports.zstd=1.6.0=py313h18e8e13_0 - bokeh=3.6.3=pyhd8ed1ab_0 - brotli=1.2.0=hed03a55_1 - brotli-bin=1.2.0=hb03c661_1 - brotli-python=1.2.0=py313hf159716_1 - bzip2=1.0.8=hda65f42_9 - - c-ares=1.34.8=hb03c661_0 + - c-ares=1.34.6=hb03c661_0 - ca-certificates=2026.6.17=hbd8a1cb_0 - cached-property=1.5.2=hd8ed1ab_2 - cached_property=1.5.2=pyha770c72_2 @@ -45,7 +45,7 @@ dependencies: - geoana=0.8.1=np2py313h0f78c12_0 - h2=4.3.0=pyhcf101f3_0 - h5py=3.16.0=nompi_py313h22c32d4_102 - - hdf5=2.1.0=nompi_h654f344_110 + - hdf5=2.1.0=nompi_hc8ae080_109 - hpack=4.2.0=pyhd8ed1ab_0 - hyperframe=6.1.0=pyhd8ed1ab_0 - icu=75.1=he02047a_0 @@ -56,7 +56,7 @@ dependencies: - kiwisolver=1.5.0=py313hc8edb43_0 - krb5=1.22.2=hbde042b_1 - lcms2=2.19.1=h0c24ade_1 - - ld_impl_linux-64=2.46.1=default_hbd61a6d_102 + - ld_impl_linux-64=2.45.1=default_hbd61a6d_102 - lerc=4.1.0=hdb68285_0 - libaec=1.1.5=h088129d_0 - libblas=3.11.0=6_h5875eb1_mkl @@ -147,15 +147,15 @@ dependencies: - tbb=2023.0.0=hab88423_2 - tblib=3.2.2=pyhcf101f3_0 - threadpoolctl=3.6.0=pyhecae5ae_0 - - tk=8.6.13=noxft_hd70dff1_3 + - tk=8.6.13=noxft_h366c992_103 - toolz=1.1.0=pyhd8ed1ab_1 - tornado=6.5.7=py313h07c4f96_0 - - tqdm=4.69.0=pyh8f84b5b_0 + - tqdm=4.68.4=pyh8f84b5b_0 - trimesh=4.12.2=pyh7b2049a_0 - typing-extensions=4.16.0=h69aa097_0 - typing-inspection=0.4.2=pyhcf101f3_2 - typing_extensions=4.16.0=pyhcf101f3_0 - - tzdata=2026c=h151e31d_0 + - tzdata=2025c=hc9c84f9_1 - urllib3=2.7.0=pyhd8ed1ab_0 - wrapt=2.2.2=py313h07c4f96_0 - xorg-libxau=1.0.12=hb03c661_1 @@ -168,10 +168,10 @@ dependencies: - zlib-ng=2.3.3=hceb46e0_1 - zstd=1.5.7=hb78ec9c_6 - pip: - - geoapps-utils == 0.7.0 --hash=sha256:97796a9a1dbaed392eb2be97a1e66f2ae894b64460bdb940f42a945e31e2c417 - - geoh5py == 0.13.1rc2 --hash=sha256:3a01ce48994bad4bd2ee0f7f97eb8bdf6bba490b11d9cbbaac12f1a0f300e389 - - grid-apps == 0.2.0 --hash=sha256:77dd1881c3467687f7b1698eefc285368f0f84a1b154651e97006b36a668fa54 - - mira-simpeg == 0.25.0.1 --hash=sha256:8bceaaf017df95ea925e20feaa2e6a5dc0f8fede434738d6493c775cb5341b38 + - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@2deafccc38a40039dc6b3f376bf0e3c9d0371498 + - geoh5py @ git+https://github.com/MiraGeoscience/geoh5py.git@3722011aa9fd8512f1bd6a3e58e2220a88ca8ce2 + - grid-apps @ git+https://github.com/MiraGeoscience/grid-apps.git@7bc33d170f03dd6da15ee531b49cdb5835b36ea4 + - mira-simpeg @ git+https://github.com/MiraGeoscience/simpeg.git@87230755b40f3961fd9b2383df191c140a29c601 variables: KMP_WARNINGS: 0 diff --git a/environments/py-3.13-win-64-dev.conda.lock.yml b/environments/py-3.13-win-64-dev.conda.lock.yml index 82852cf08..969b1661e 100644 --- a/environments/py-3.13-win-64-dev.conda.lock.yml +++ b/environments/py-3.13-win-64-dev.conda.lock.yml @@ -1,13 +1,13 @@ # Generated by conda-lock. # platform: win-64 -# input_hash: 9b56450802b028b33a88fec3ccdb330045ac53ddb06ca386fd71b21c5676fe48 +# input_hash: d81085ff8b943e3347da9c2174e4427cd5736c1d273bc4a6b4c77ebac25efae5 channels: - conda-forge - nodefaults dependencies: - _openmp_mutex=4.5=20_gnu - - _python_abi3_support=1.0=hd8ed1ab_3 + - _python_abi3_support=1.0=hd8ed1ab_2 - alabaster=0.7.16=pyhd8ed1ab_0 - annotated-types=0.7.0=pyhd8ed1ab_1 - anyio=4.14.2=pyhcf101f3_0 @@ -16,18 +16,18 @@ dependencies: - arrow=1.4.0=pyhcf101f3_0 - asciitree=0.3.3=py_2 - astroid=4.0.4=py313hfa70ccb_0 - - asttokens=3.0.2=pyhd8ed1ab_0 + - asttokens=3.0.1=pyhd8ed1ab_0 - async-lru=2.3.0=pyhcf101f3_0 - attrs=26.1.0=pyhcf101f3_0 - - aws-c-auth=0.10.4=hc6e9107_1 - - aws-c-cal=0.9.14=h032d170_4 - - aws-c-common=0.14.2=hfd05255_0 - - aws-c-compression=0.3.2=h1da161f_4 - - aws-c-http=0.11.0=h667fc28_4 - - aws-c-io=0.27.3=hbdc738f_1 - - aws-c-s3=0.12.8=hd7ee1a1_1 - - aws-c-sdkutils=0.2.7=h1da161f_2 - - aws-checksums=0.2.10=h1da161f_4 + - aws-c-auth=0.10.3=h0a1c2aa_4 + - aws-c-cal=0.9.14=h92e57be_3 + - aws-c-common=0.14.1=hfd05255_0 + - aws-c-compression=0.3.2=h4c63076_3 + - aws-c-http=0.11.0=h2c07012_3 + - aws-c-io=0.26.3=hf70332f_6 + - aws-c-s3=0.12.6=hcb9225d_1 + - aws-c-sdkutils=0.2.7=h4c63076_1 + - aws-checksums=0.2.10=h4c63076_3 - babel=2.18.0=pyhcf101f3_1 - backports.zstd=1.6.0=py313h2a31948_0 - beautifulsoup4=4.15.0=pyha770c72_0 @@ -41,7 +41,7 @@ dependencies: - ca-certificates=2026.6.17=h4c7d964_0 - cached-property=1.5.2=hd8ed1ab_2 - cached_property=1.5.2=pyha770c72_2 - - certifi=2026.7.22=pyhd8ed1ab_0 + - certifi=2026.6.17=pyhd8ed1ab_0 - cffi=2.1.0=py313h5ea7bf4_0 - charset-normalizer=3.4.9=pyhd8ed1ab_0 - click=8.4.2=pyh6dadd2b_0 @@ -49,7 +49,7 @@ dependencies: - colorama=0.4.6=pyhd8ed1ab_1 - comm=0.2.3=pyhe01879c_0 - contourpy=1.3.3=py313h1a38498_4 - - coverage=7.15.2=py313hd650c13_0 + - coverage=7.15.0=py313hd650c13_0 - cpython=3.13.14=py313hd8ed1ab_100 - cycler=0.12.1=pyhcf101f3_2 - cytoolz=1.1.0=py313h5ea7bf4_2 @@ -74,12 +74,12 @@ dependencies: - h11=0.16.0=pyhcf101f3_1 - h2=4.3.0=pyhcf101f3_0 - h5py=3.16.0=nompi_py313hd050a09_102 - - hdf5=2.1.0=nompi_h0d3e4b2_110 + - hdf5=2.1.0=nompi_h0065017_109 - hpack=4.2.0=pyhd8ed1ab_0 - httpcore=1.0.9=pyh29332c3_0 - httpx=0.28.1=pyhd8ed1ab_0 - hyperframe=6.1.0=pyhd8ed1ab_0 - - icu=78.3=h637d24d_1 + - icu=78.3=h637d24d_0 - idna=3.18=pyhcf101f3_0 - imagesize=2.0.0=pyhd8ed1ab_0 - importlib-metadata=9.0.0=pyhcf101f3_0 @@ -100,18 +100,18 @@ dependencies: - jsonschema-specifications=2025.9.1=pyhcf101f3_0 - jsonschema-with-format-nongpl=4.26.0=hcf101f3_0 - jupyter-book=2.1.6=pyhcf101f3_0 - - jupyter-builder=1.1.1=pyhcf101f3_0 + - jupyter-builder=1.1.0=pyhcf101f3_0 - jupyter-lsp=2.3.1=pyhcf101f3_0 - jupyter_client=8.9.1=pyhcf101f3_0 - jupyter_core=5.9.1=pyh6dadd2b_0 - jupyter_events=0.12.1=pyhcf101f3_0 - jupyter_server=2.20.0=pyhcf101f3_0 - jupyter_server_terminals=0.5.4=pyhcf101f3_0 - - jupyterlab=4.6.2=pyhd8ed1ab_0 + - jupyterlab=4.6.1=pyhd8ed1ab_0 - jupyterlab_pygments=0.3.0=pyhd8ed1ab_2 - jupyterlab_server=2.28.0=pyhcf101f3_0 - jupyterlab_widgets=1.1.11=pyhd8ed1ab_0 - - jupytext=1.19.5=pyh0398c0e_0 + - jupytext=1.19.4=pyh0398c0e_0 - kiwisolver=1.5.0=py313h1a38498_0 - krb5=1.22.2=h719d79b_1 - lark=1.3.1=pyhd8ed1ab_0 @@ -160,7 +160,7 @@ dependencies: - mccabe=0.7.0=pyhd8ed1ab_1 - mdit-py-plugins=0.6.1=pyhd8ed1ab_0 - mdurl=0.1.2=pyhd8ed1ab_1 - - mistune=3.3.4=pyhcf101f3_0 + - mistune=3.3.3=pyhcf101f3_0 - mkl=2025.3.1=hac47afa_13 - msgpack-python=1.2.1=py313h1a38498_1 - mumps-seq=5.8.2=h607cc0b_2 @@ -172,7 +172,7 @@ dependencies: - nbformat=5.10.4=pyhd8ed1ab_1 - nest-asyncio2=1.7.2=pyhcf101f3_0 - nodejs=26.5.0=h80d1838_0 - - notebook=7.6.1=pyhcf101f3_0 + - notebook=7.6.0=pyhcf101f3_0 - notebook-shim=0.2.4=pyhd8ed1ab_1 - numcodecs=0.15.1=py313hc90dcd4_1 - numpy=2.4.6=py313ha8dc839_0 @@ -189,7 +189,7 @@ dependencies: - partd=1.4.2=pyhd8ed1ab_0 - pillow=12.3.0=py313h38f99e1_0 - pip=26.1.2=pyh145f28c_0 - - platformdirs=4.11.0=pyhcf101f3_0 + - platformdirs=4.10.0=pyhcf101f3_0 - pluggy=1.6.0=pyhf9edf01_1 - prometheus_client=0.25.0=pyhd8ed1ab_0 - prompt-toolkit=3.0.52=pyha770c72_0 @@ -237,7 +237,7 @@ dependencies: - sniffio=1.3.1=pyhd8ed1ab_2 - snowballstemmer=3.1.1=pyhd8ed1ab_0 - sortedcontainers=2.4.0=pyhd8ed1ab_1 - - soupsieve=2.9.1=pyhd8ed1ab_0 + - soupsieve=2.8.4=pyhd8ed1ab_0 - sphinx=5.3.0=pyhd8ed1ab_0 - sphinxcontrib-applehelp=2.0.0=pyhd8ed1ab_1 - sphinxcontrib-devhelp=2.0.0=pyhd8ed1ab_1 @@ -251,19 +251,19 @@ dependencies: - terminado=0.18.1=pyh6dadd2b_1 - threadpoolctl=3.6.0=pyhecae5ae_0 - tinycss2=1.4.0=pyhd8ed1ab_0 - - tk=8.6.13=h967ab96_3 + - tk=8.6.13=h6ed50ae_3 - tomli=2.4.1=pyhcf101f3_0 - - tomlkit=0.15.1=pyhcf101f3_0 + - tomlkit=0.15.0=pyha770c72_0 - toolz=1.1.0=pyhd8ed1ab_1 - tornado=6.5.7=py313h5ea7bf4_0 - - tqdm=4.69.0=pyha7b4d00_0 + - tqdm=4.68.4=pyha7b4d00_0 - traitlets=5.15.1=pyhcf101f3_0 - trimesh=4.12.2=pyh7b2049a_0 - typing-extensions=4.16.0=h69aa097_0 - typing-inspection=0.4.2=pyhcf101f3_2 - typing_extensions=4.16.0=pyhcf101f3_0 - typing_utils=0.1.0=pyhd8ed1ab_1 - - tzdata=2026c=h151e31d_0 + - tzdata=2025c=hc9c84f9_1 - ucrt=10.0.26100.0=h57928b3_0 - uri-template=1.3.0=pyhd8ed1ab_1 - urllib3=2.7.0=pyhd8ed1ab_0 @@ -290,10 +290,10 @@ dependencies: - zlib-ng=2.3.3=h0261ad2_1 - zstd=1.5.7=h534d264_6 - pip: - - geoapps-utils == 0.7.0 --hash=sha256:97796a9a1dbaed392eb2be97a1e66f2ae894b64460bdb940f42a945e31e2c417 - - geoh5py == 0.13.1rc2 --hash=sha256:3a01ce48994bad4bd2ee0f7f97eb8bdf6bba490b11d9cbbaac12f1a0f300e389 - - grid-apps == 0.2.0 --hash=sha256:77dd1881c3467687f7b1698eefc285368f0f84a1b154651e97006b36a668fa54 - - mira-simpeg == 0.25.0.1 --hash=sha256:8bceaaf017df95ea925e20feaa2e6a5dc0f8fede434738d6493c775cb5341b38 + - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@2deafccc38a40039dc6b3f376bf0e3c9d0371498 + - geoh5py @ git+https://github.com/MiraGeoscience/geoh5py.git@3722011aa9fd8512f1bd6a3e58e2220a88ca8ce2 + - grid-apps @ git+https://github.com/MiraGeoscience/grid-apps.git@7bc33d170f03dd6da15ee531b49cdb5835b36ea4 + - mira-simpeg @ git+https://github.com/MiraGeoscience/simpeg.git@87230755b40f3961fd9b2383df191c140a29c601 variables: KMP_WARNINGS: 0 diff --git a/environments/py-3.13-win-64.conda.lock.yml b/environments/py-3.13-win-64.conda.lock.yml index af2748f93..bf9459e94 100644 --- a/environments/py-3.13-win-64.conda.lock.yml +++ b/environments/py-3.13-win-64.conda.lock.yml @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: win-64 -# input_hash: 9b56450802b028b33a88fec3ccdb330045ac53ddb06ca386fd71b21c5676fe48 +# input_hash: d81085ff8b943e3347da9c2174e4427cd5736c1d273bc4a6b4c77ebac25efae5 channels: - conda-forge @@ -9,15 +9,15 @@ dependencies: - _openmp_mutex=4.5=20_gnu - annotated-types=0.7.0=pyhd8ed1ab_1 - asciitree=0.3.3=py_2 - - aws-c-auth=0.10.4=hc6e9107_1 - - aws-c-cal=0.9.14=h032d170_4 - - aws-c-common=0.14.2=hfd05255_0 - - aws-c-compression=0.3.2=h1da161f_4 - - aws-c-http=0.11.0=h667fc28_4 - - aws-c-io=0.27.3=hbdc738f_1 - - aws-c-s3=0.12.8=hd7ee1a1_1 - - aws-c-sdkutils=0.2.7=h1da161f_2 - - aws-checksums=0.2.10=h1da161f_4 + - aws-c-auth=0.10.3=h0a1c2aa_4 + - aws-c-cal=0.9.14=h92e57be_3 + - aws-c-common=0.14.1=hfd05255_0 + - aws-c-compression=0.3.2=h4c63076_3 + - aws-c-http=0.11.0=h2c07012_3 + - aws-c-io=0.26.3=hf70332f_6 + - aws-c-s3=0.12.6=hcb9225d_1 + - aws-c-sdkutils=0.2.7=h4c63076_1 + - aws-checksums=0.2.10=h4c63076_3 - backports.zstd=1.6.0=py313h2a31948_0 - bokeh=3.6.3=pyhd8ed1ab_0 - brotli=1.2.0=h2d644bc_1 @@ -45,10 +45,10 @@ dependencies: - geoana=0.8.1=np2py313hedd11bf_0 - h2=4.3.0=pyhcf101f3_0 - h5py=3.16.0=nompi_py313hd050a09_102 - - hdf5=2.1.0=nompi_h0d3e4b2_110 + - hdf5=2.1.0=nompi_h0065017_109 - hpack=4.2.0=pyhd8ed1ab_0 - hyperframe=6.1.0=pyhd8ed1ab_0 - - icu=78.3=h637d24d_1 + - icu=78.3=h637d24d_0 - importlib-metadata=9.0.0=pyhcf101f3_0 - jinja2=3.1.6=pyhcf101f3_1 - joblib=1.5.3=pyhd8ed1ab_0 @@ -133,15 +133,15 @@ dependencies: - tbb=2023.0.0=hd3d4ead_2 - tblib=3.2.2=pyhcf101f3_0 - threadpoolctl=3.6.0=pyhecae5ae_0 - - tk=8.6.13=h967ab96_3 + - tk=8.6.13=h6ed50ae_3 - toolz=1.1.0=pyhd8ed1ab_1 - tornado=6.5.7=py313h5ea7bf4_0 - - tqdm=4.69.0=pyha7b4d00_0 + - tqdm=4.68.4=pyha7b4d00_0 - trimesh=4.12.2=pyh7b2049a_0 - typing-extensions=4.16.0=h69aa097_0 - typing-inspection=0.4.2=pyhcf101f3_2 - typing_extensions=4.16.0=pyhcf101f3_0 - - tzdata=2026c=h151e31d_0 + - tzdata=2025c=hc9c84f9_1 - ucrt=10.0.26100.0=h57928b3_0 - urllib3=2.7.0=pyhd8ed1ab_0 - vc=14.5=h1b7c187_39 @@ -160,10 +160,10 @@ dependencies: - zlib-ng=2.3.3=h0261ad2_1 - zstd=1.5.7=h534d264_6 - pip: - - geoapps-utils == 0.7.0 --hash=sha256:97796a9a1dbaed392eb2be97a1e66f2ae894b64460bdb940f42a945e31e2c417 - - geoh5py == 0.13.1rc2 --hash=sha256:3a01ce48994bad4bd2ee0f7f97eb8bdf6bba490b11d9cbbaac12f1a0f300e389 - - grid-apps == 0.2.0 --hash=sha256:77dd1881c3467687f7b1698eefc285368f0f84a1b154651e97006b36a668fa54 - - mira-simpeg == 0.25.0.1 --hash=sha256:8bceaaf017df95ea925e20feaa2e6a5dc0f8fede434738d6493c775cb5341b38 + - geoapps-utils @ git+https://github.com/MiraGeoscience/geoapps-utils.git@2deafccc38a40039dc6b3f376bf0e3c9d0371498 + - geoh5py @ git+https://github.com/MiraGeoscience/geoh5py.git@3722011aa9fd8512f1bd6a3e58e2220a88ca8ce2 + - grid-apps @ git+https://github.com/MiraGeoscience/grid-apps.git@7bc33d170f03dd6da15ee531b49cdb5835b36ea4 + - mira-simpeg @ git+https://github.com/MiraGeoscience/simpeg.git@87230755b40f3961fd9b2383df191c140a29c601 variables: KMP_WARNINGS: 0 diff --git a/py-3.12.conda-lock.yml b/py-3.12.conda-lock.yml index ef271ecd8..69e42ef02 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: 6e2adf071404327a6402729138200c74fea182b48456560110dcf57a2bfb8744 - linux-64: 8ffe2b7bd402c394a70b91fad1304325a8ee68470e2f1565a7a098270cee1b60 + win-64: b09e66847f78fcc9809b6d746d19563387f7cb275b1d47dddaf7b7a70cff0686 + linux-64: d4e89447ddc89490b9baa6153c503886506d9dbc1b81466f4cc3e6a28d370325 channels: - url: conda-forge used_env_vars: [] @@ -61,10 +61,10 @@ package: dependencies: cpython: '' python-gil: '' - url: https://repo.prefix.dev/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda + url: https://repo.prefix.dev/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda hash: - md5: 3845f3d75991bae0fb90884662f4327c - sha256: 2a7204314663eeda5dec482a956f0e2eaf289bd5b9953eaaaad0e81aa64638f2 + md5: aaa2a381ccc56eac91d63b6c1240312f + sha256: a3967b937b9abf0f2a99f3173fa4630293979bd1644709d89580e7c62a544661 category: dev optional: true - name: _python_abi3_support @@ -74,10 +74,10 @@ package: dependencies: cpython: '' python-gil: '' - url: https://repo.prefix.dev/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda + url: https://repo.prefix.dev/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda hash: - md5: 3845f3d75991bae0fb90884662f4327c - sha256: 2a7204314663eeda5dec482a956f0e2eaf289bd5b9953eaaaad0e81aa64638f2 + md5: aaa2a381ccc56eac91d63b6c1240312f + sha256: a3967b937b9abf0f2a99f3173fa4630293979bd1644709d89580e7c62a544661 category: dev optional: true - name: alabaster @@ -300,27 +300,27 @@ package: category: dev optional: true - name: asttokens - version: 3.0.2 + version: 3.0.1 manager: conda platform: linux-64 dependencies: python: '>=3.10' - url: https://repo.prefix.dev/conda-forge/noarch/asttokens-3.0.2-pyhd8ed1ab_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda hash: - md5: 89d495168582cb00428dad699d149624 - sha256: fbbd8ce60cbd5c16f3fe559eb644551f94285caff30985ea961ff851c1cf25ac + md5: 9673a61a297b00016442e022d689faa6 + sha256: ee4da0f3fe9d59439798ee399ef3e482791e48784873d546e706d0935f9ff010 category: dev optional: true - name: asttokens - version: 3.0.2 + version: 3.0.1 manager: conda platform: win-64 dependencies: python: '>=3.10' - url: https://repo.prefix.dev/conda-forge/noarch/asttokens-3.0.2-pyhd8ed1ab_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda hash: - md5: 89d495168582cb00428dad699d149624 - sha256: fbbd8ce60cbd5c16f3fe559eb644551f94285caff30985ea961ff851c1cf25ac + md5: 9673a61a297b00016442e022d689faa6 + sha256: ee4da0f3fe9d59439798ee399ef3e482791e48784873d546e706d0935f9ff010 category: dev optional: true - name: async-lru @@ -374,40 +374,40 @@ package: category: dev optional: true - name: aws-c-auth - version: 0.10.4 + version: 0.10.3 manager: conda platform: linux-64 dependencies: __glibc: '>=2.17,<3.0.a0' aws-c-cal: '>=0.9.14,<0.9.15.0a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' aws-c-http: '>=0.11.0,<0.11.1.0a0' - aws-c-io: '>=0.27.3,<0.27.4.0a0' + aws-c-io: '>=0.26.3,<0.26.4.0a0' aws-c-sdkutils: '>=0.2.7,<0.2.8.0a0' libgcc: '>=14' - url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-auth-0.10.4-hb7a77c6_1.conda + url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-auth-0.10.3-hdc83ae9_4.conda hash: - md5: 4347d5ffdf34adf9c5edd10837727d96 - sha256: 845fe32ee750d4a4d3efdb1d95a8c29c3388e3ea9787b77341ec4abe4e198b11 + md5: 00840a04f8ec0033ed380f150fcd1c93 + sha256: d7746767f17d3bf12cef8e69b7d6fa1ba975a25d4a1c3250bc7031020f0eb94d category: main optional: false - name: aws-c-auth - version: 0.10.4 + version: 0.10.3 manager: conda platform: win-64 dependencies: aws-c-cal: '>=0.9.14,<0.9.15.0a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' aws-c-http: '>=0.11.0,<0.11.1.0a0' - aws-c-io: '>=0.27.3,<0.27.4.0a0' + aws-c-io: '>=0.26.3,<0.26.4.0a0' aws-c-sdkutils: '>=0.2.7,<0.2.8.0a0' ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/aws-c-auth-0.10.4-hc6e9107_1.conda + url: https://repo.prefix.dev/conda-forge/win-64/aws-c-auth-0.10.3-h0a1c2aa_4.conda hash: - md5: f7069cdc81f7a5e781f1faf6aab6c171 - sha256: cf7ab8abaac6b462463310412cc37d087a767a95920809cbe6dc0a08fe4bcfbd + md5: f191830ba03429449e6de9ba5f00a1b7 + sha256: a87f863ffd39c69340fbda750eafde23097681e45bb08c04a8566e87b327a04a category: main optional: false - name: aws-c-cal @@ -416,13 +416,13 @@ package: platform: linux-64 dependencies: __glibc: '>=2.17,<3.0.a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' libgcc: '>=14' openssl: '>=3.5.7,<4.0a0' - url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-cal-0.9.14-h2aa3ae6_4.conda + url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-cal-0.9.14-h6e18c7b_3.conda hash: - md5: 9c6072e7b882d35ac956c07e493d6a9e - sha256: a67c944be1728f576c02fe8f28b0cbb78b5353415075db3da4ef71bc9dd8c00c + md5: d1b68989381f8cbf582ba7a5e3bb5b02 + sha256: 594d8c27ea57997863499a8e55ac8fb6587cdd62639445da91e1a88756488a88 category: main optional: false - name: aws-c-cal @@ -430,41 +430,41 @@ package: manager: conda platform: win-64 dependencies: - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/aws-c-cal-0.9.14-h032d170_4.conda + url: https://repo.prefix.dev/conda-forge/win-64/aws-c-cal-0.9.14-h92e57be_3.conda hash: - md5: fda8fa85ef5d8570d5a0aadea688bb82 - sha256: cda33360c71ab9a4b4e269004a5672d712fa1ae581ecca0404181805ce6762ce + md5: 5057d17412a615efce912b41c6a26d82 + sha256: dd706664dd7ab021320634481f5b126f774c6b0526eab24b79f9a6a3961ea6ed category: main optional: false - name: aws-c-common - version: 0.14.2 + version: 0.14.1 manager: conda platform: linux-64 dependencies: __glibc: '>=2.17,<3.0.a0' libgcc: '>=14' - url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-common-0.14.2-hb03c661_0.conda + url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-common-0.14.1-hb03c661_0.conda hash: - md5: f3e0b2e044485ae90d4a59c77e7a0182 - sha256: 701398f1c9978c41e93cb0947869a66b7f8004e9d6e548d63d11aedae83cfb02 + md5: 4faa1e8d0cbc9137e8b0109c78931234 + sha256: a83c1a18754fe0f466d3a230106d6a52847c915c625b0be6b9f4677f46024a6c category: main optional: false - name: aws-c-common - version: 0.14.2 + version: 0.14.1 manager: conda platform: win-64 dependencies: ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/aws-c-common-0.14.2-hfd05255_0.conda + url: https://repo.prefix.dev/conda-forge/win-64/aws-c-common-0.14.1-hfd05255_0.conda hash: - md5: 75b4445fec6477b271920f87830d22a3 - sha256: 63fc3f34a3b3d21d5f6527ba5136e132f3ad50541d4d16e385d03f4e47e1db2b + md5: bda6f415d6bf1be6c3de28477c3c6d21 + sha256: 803a59e9d4b324dc49aa9bc25f601f0766adbe344acf345015a1437210d9157e category: main optional: false - name: aws-c-compression @@ -473,12 +473,12 @@ package: platform: linux-64 dependencies: __glibc: '>=2.17,<3.0.a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' libgcc: '>=14' - url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-compression-0.3.2-h720e601_4.conda + url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-compression-0.3.2-haaf8e00_3.conda hash: - md5: c9be3f5854f349ae77a1a174b59e11a8 - sha256: 8e0a5513cfc42df8bd16adbd8e83a37d3ca89b8e04cb20eb04eb177bbbfea365 + md5: 953cf7d978432cc1703395b3e97b2776 + sha256: 5f40faa0c3661a231e1a01e4abf287621509ae60fb95c92fdafdf7a2dd3d0da7 category: main optional: false - name: aws-c-compression @@ -486,14 +486,14 @@ package: manager: conda platform: win-64 dependencies: - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/aws-c-compression-0.3.2-h1da161f_4.conda + url: https://repo.prefix.dev/conda-forge/win-64/aws-c-compression-0.3.2-h4c63076_3.conda hash: - md5: 996e5c7da8f9e255eac094d2413b40c3 - sha256: 97eaf03572b61d118616e8ee8459823c7b0f5dc26295bbab3ac3f6d671f1547a + md5: 5ee3f4b0f44948e8381d3ed85b968c6f + sha256: a41fdc98f48defd807d9befeec975cb772ceeb1ab1d944ecb7d73f2f13882d89 category: main optional: false - name: aws-c-http @@ -503,14 +503,14 @@ package: dependencies: __glibc: '>=2.17,<3.0.a0' aws-c-cal: '>=0.9.14,<0.9.15.0a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' aws-c-compression: '>=0.3.2,<0.3.3.0a0' - aws-c-io: '>=0.27.3,<0.27.4.0a0' + aws-c-io: '>=0.26.3,<0.26.4.0a0' libgcc: '>=14' - url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-http-0.11.0-h38ae05a_4.conda + url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-http-0.11.0-h434eaf2_3.conda hash: - md5: 97f2799ae6ff7b6f52dfa321fef9676b - sha256: 4b939b4a76a11c9ec50c891ae9bf232da8dcaf8797701df1e79ae51c988be2d4 + md5: 5d0c1e7ef42363ed72969ed488703644 + sha256: 8a21457233210ca69bae462d932d0513ccf6329a0914b2f454b42aa936ddf819 category: main optional: false - name: aws-c-http @@ -519,88 +519,88 @@ package: platform: win-64 dependencies: aws-c-cal: '>=0.9.14,<0.9.15.0a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' aws-c-compression: '>=0.3.2,<0.3.3.0a0' - aws-c-io: '>=0.27.3,<0.27.4.0a0' + aws-c-io: '>=0.26.3,<0.26.4.0a0' ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/aws-c-http-0.11.0-h667fc28_4.conda + url: https://repo.prefix.dev/conda-forge/win-64/aws-c-http-0.11.0-h2c07012_3.conda hash: - md5: c17a284b159959a8639298ed7da8ad0b - sha256: 304587d77daccde630fbdbb8ae2f3994cf583427f710c60d62e88bc97c4ff013 + md5: 7eb24c10ea05776b52dc4659dffd9a38 + sha256: 0957c39b445ab342bf0747334e4c823212c6e3297ac41f13d6bd96b221a51e0a category: main optional: false - name: aws-c-io - version: 0.27.3 + version: 0.26.3 manager: conda platform: linux-64 dependencies: __glibc: '>=2.17,<3.0.a0' aws-c-cal: '>=0.9.14,<0.9.15.0a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' libgcc: '>=14' s2n: '>=1.7.5,<1.7.6.0a0' - url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-io-0.27.3-h6f4d18d_1.conda + url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-io-0.26.3-ha0b0e19_6.conda hash: - md5: bd19b685b88557830f1a617a6d404eb2 - sha256: 94c32ca672ce290e250aea1cfb92582cca4658bdc3ec7cb1ceef254f34451595 + md5: bec59731db9cc0965f270a6e33a8db73 + sha256: 5406c95965698b7998f9288a05a5aa138d7cc6af82e4a823d3afaca09170e06c category: main optional: false - name: aws-c-io - version: 0.27.3 + version: 0.26.3 manager: conda platform: win-64 dependencies: aws-c-cal: '>=0.9.14,<0.9.15.0a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/aws-c-io-0.27.3-hbdc738f_1.conda + url: https://repo.prefix.dev/conda-forge/win-64/aws-c-io-0.26.3-hf70332f_6.conda hash: - md5: 579600368b15fb523d69e2a3f8a9bc55 - sha256: 36816ff394ee736fcf705db5b1c1491c98f6f77ef84791d73b71e78154df402d + md5: 7902637f56f67f42d298763e5d88a9e6 + sha256: 843e1d7f512adb0910c59ef0ae7772c87c8524878febfb793bf0106d813e82eb category: main optional: false - name: aws-c-s3 - version: 0.12.8 + version: 0.12.6 manager: conda platform: linux-64 dependencies: __glibc: '>=2.17,<3.0.a0' - aws-c-auth: '>=0.10.4,<0.10.5.0a0' + aws-c-auth: '>=0.10.3,<0.10.4.0a0' aws-c-cal: '>=0.9.14,<0.9.15.0a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' aws-c-http: '>=0.11.0,<0.11.1.0a0' - aws-c-io: '>=0.27.3,<0.27.4.0a0' + aws-c-io: '>=0.26.3,<0.26.4.0a0' aws-checksums: '>=0.2.10,<0.2.11.0a0' libgcc: '>=14' openssl: '>=3.5.7,<4.0a0' - url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-s3-0.12.8-h46fcd08_1.conda + url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-s3-0.12.6-hd767fb6_1.conda hash: - md5: 706aa99414d18db5b23475b45cc93a0b - sha256: a423bffbb0e6cacb5e2a0861b918ba3180e5132509afb1faf225ee9f0ec8f981 + md5: aa4756841efe742938984663ef2397ef + sha256: 81abe7f421c630b168454f40d4daf58d73b09aab78f8d99b5d6c8e2d132bcfc7 category: main optional: false - name: aws-c-s3 - version: 0.12.8 + version: 0.12.6 manager: conda platform: win-64 dependencies: - aws-c-auth: '>=0.10.4,<0.10.5.0a0' + aws-c-auth: '>=0.10.3,<0.10.4.0a0' aws-c-cal: '>=0.9.14,<0.9.15.0a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' aws-c-http: '>=0.11.0,<0.11.1.0a0' - aws-c-io: '>=0.27.3,<0.27.4.0a0' + aws-c-io: '>=0.26.3,<0.26.4.0a0' aws-checksums: '>=0.2.10,<0.2.11.0a0' ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/aws-c-s3-0.12.8-hd7ee1a1_1.conda + url: https://repo.prefix.dev/conda-forge/win-64/aws-c-s3-0.12.6-hcb9225d_1.conda hash: - md5: 4ac02fa15bf3809101e2eeb340b6078c - sha256: c79e62931e163b65a01786d5ed7156ecca2ab748b7c7835756d34e438035706a + md5: bd68a03d89eb296fe3b41ee9a6696341 + sha256: b37e42dda013641efd411b5f5cd85e49dbc874943b5a7b4cc2628e3c2cb1837e category: main optional: false - name: aws-c-sdkutils @@ -609,12 +609,12 @@ package: platform: linux-64 dependencies: __glibc: '>=2.17,<3.0.a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' libgcc: '>=14' - url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-sdkutils-0.2.7-h720e601_2.conda + url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-sdkutils-0.2.7-haaf8e00_1.conda hash: - md5: 816f62fa82532118ebb2398382090945 - sha256: e419e179e04021fc680d98af23fac4b4c2369cea61171087e57a734e968dd9bd + md5: 2eb431d738f8e5ee90aff8ac5d255456 + sha256: e2653b11b1b222c9189cdf0362be5e2d347589d9ad5dfad25509860852e3ba85 category: main optional: false - name: aws-c-sdkutils @@ -622,14 +622,14 @@ package: manager: conda platform: win-64 dependencies: - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/aws-c-sdkutils-0.2.7-h1da161f_2.conda + url: https://repo.prefix.dev/conda-forge/win-64/aws-c-sdkutils-0.2.7-h4c63076_1.conda hash: - md5: 882d834f12e0abf4c7a0e9bb0c72e281 - sha256: d5da00123d9ceb082e61fd5bf82fff9cd5bec0b8e1ae91454f29155a59499bf5 + md5: 4914281d91c9385ae6a57be69489cc16 + sha256: fc4a86838f740f1639f12fe39eaa6550126dd7330fbd0b7c4b85246f40e4ea2c category: main optional: false - name: aws-checksums @@ -638,12 +638,12 @@ package: platform: linux-64 dependencies: __glibc: '>=2.17,<3.0.a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' libgcc: '>=14' - url: https://repo.prefix.dev/conda-forge/linux-64/aws-checksums-0.2.10-h720e601_4.conda + url: https://repo.prefix.dev/conda-forge/linux-64/aws-checksums-0.2.10-haaf8e00_3.conda hash: - md5: 24ee781effc5779206a80139323c9caf - sha256: 6cc8617854a43040291dea791fe111ba408e7a5bbd9ee9e5a99375da7a16846b + md5: 394a24d9d451020541ba28c27fc6ef16 + sha256: 90c4d073871f309cc35bc66da55eb169c9ac27790c51670963fd0a36a6e46f0d category: main optional: false - name: aws-checksums @@ -651,14 +651,14 @@ package: manager: conda platform: win-64 dependencies: - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/aws-checksums-0.2.10-h1da161f_4.conda + url: https://repo.prefix.dev/conda-forge/win-64/aws-checksums-0.2.10-h4c63076_3.conda hash: - md5: a3c81085892f2983979836f2937291ce - sha256: 2227fa77f395f1b4699533709e7ef140a8292fccb31376ec5498565c7ad33225 + md5: 3a97d5f5d53f730c6d03a3034f99f00b + sha256: e403379e7686613f198474f56dc7019bbe5cc080baa6dd55ceab6270a193e53e category: main optional: false - name: babel @@ -964,16 +964,16 @@ package: category: main optional: false - name: c-ares - version: 1.34.8 + version: 1.34.6 manager: conda platform: linux-64 dependencies: __glibc: '>=2.17,<3.0.a0' libgcc: '>=14' - url: https://repo.prefix.dev/conda-forge/linux-64/c-ares-1.34.8-hb03c661_0.conda + url: https://repo.prefix.dev/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda hash: - md5: 6130ad6705adc993b5d8482b7f66e01f - sha256: dee93a82d045f9aa8277829aa465224afa3c7a6ac18388de66099b2be7f705e7 + md5: 920bb03579f15389b9e512095ad995b7 + sha256: cc9accf72fa028d31c2a038460787751127317dcfa991f8d1f1babf216bb454e category: main optional: false - name: ca-certificates @@ -1049,27 +1049,27 @@ package: category: main optional: false - name: certifi - version: 2026.7.22 + version: 2026.6.17 manager: conda platform: linux-64 dependencies: python: '>=3.10' - url: https://repo.prefix.dev/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/certifi-2026.6.17-pyhd8ed1ab_0.conda hash: - md5: 37e13edbe3b48f1095a9d085ef9cd83b - sha256: fb167de4388e64e52aa3907ed099afab944c1fa6e5f74b281a312dae1bcf7f3b + md5: c13824fedced67005d3832c152fe9c2f + sha256: 6c13620e458ba43278379d0cdacc30c497336bddfda81681fd50d114a65c702f category: dev optional: true - name: certifi - version: 2026.7.22 + version: 2026.6.17 manager: conda platform: win-64 dependencies: python: '>=3.10' - url: https://repo.prefix.dev/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/certifi-2026.6.17-pyhd8ed1ab_0.conda hash: - md5: 37e13edbe3b48f1095a9d085ef9cd83b - sha256: fb167de4388e64e52aa3907ed099afab944c1fa6e5f74b281a312dae1bcf7f3b + md5: c13824fedced67005d3832c152fe9c2f + sha256: 6c13620e458ba43278379d0cdacc30c497336bddfda81681fd50d114a65c702f category: dev optional: true - name: cffi @@ -1264,7 +1264,7 @@ package: category: main optional: false - name: coverage - version: 7.15.2 + version: 7.15.0 manager: conda platform: linux-64 dependencies: @@ -1273,14 +1273,14 @@ package: python: '>=3.12,<3.13.0a0' python_abi: 3.12.* tomli: '' - url: https://repo.prefix.dev/conda-forge/linux-64/coverage-7.15.2-py312h8a5da7c_0.conda + url: https://repo.prefix.dev/conda-forge/linux-64/coverage-7.15.0-py312h8a5da7c_0.conda hash: - md5: 11e8cba550d02296c444316f6ebd5255 - sha256: e89d003aafa069ff41885fb798495a411e8f99f10929c490457df0dcd255f964 + md5: 81c362e153956e8b0ab533387443ba27 + sha256: 260364f80ef8c0fbbc244330ac29f863c3c76ff27551a3cd87a45af39a3628aa category: dev optional: true - name: coverage - version: 7.15.2 + version: 7.15.0 manager: conda platform: win-64 dependencies: @@ -1290,10 +1290,10 @@ package: ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/coverage-7.15.2-py312h05f76fc_0.conda + url: https://repo.prefix.dev/conda-forge/win-64/coverage-7.15.0-py312h05f76fc_0.conda hash: - md5: eb04595bd0c3634079ba51980657d75e - sha256: 5685583c45c43fafef253a65baf9e9d45863fbf5b9b8a970e2db0f39ad2c16e3 + md5: c50308d0eaca01831d886b5f4edd9580 + sha256: 27d7a0840600bf0c2fe124c29701891ca3f7626377701f838ae7a29b61b6a445 category: dev optional: true - name: cpython @@ -2014,11 +2014,11 @@ package: platform: linux-64 dependencies: __glibc: '>=2.17,<3.0.a0' - aws-c-auth: '>=0.10.4,<0.10.5.0a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-auth: '>=0.10.3,<0.10.4.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' aws-c-http: '>=0.11.0,<0.11.1.0a0' - aws-c-io: '>=0.27.3,<0.27.4.0a0' - aws-c-s3: '>=0.12.8,<0.12.9.0a0' + aws-c-io: '>=0.26.3,<0.26.4.0a0' + aws-c-s3: '>=0.12.6,<0.12.7.0a0' aws-c-sdkutils: '>=0.2.7,<0.2.8.0a0' libaec: '>=1.1.5,<2.0a0' libcurl: '>=8.21.0,<9.0a0' @@ -2028,10 +2028,10 @@ package: libstdcxx: '>=14' libzlib: '>=1.3.2,<2.0a0' openssl: '>=3.5.7,<4.0a0' - url: https://repo.prefix.dev/conda-forge/linux-64/hdf5-2.1.0-nompi_h654f344_110.conda + url: https://repo.prefix.dev/conda-forge/linux-64/hdf5-2.1.0-nompi_hc8ae080_109.conda hash: - md5: 58484580a0f626dbe7d5145f014dbfd4 - sha256: 548411cb10baf1122c46cfef555936c385137d3637b75bd322e7574eda933842 + md5: 85e9d531a6f215a5002fd2174e2dcb35 + sha256: 6aa4790c0cdaf5bda8fdd7049c6645e6e456b368acccc8dc1c08336f9f75868f category: main optional: false - name: hdf5 @@ -2039,11 +2039,11 @@ package: manager: conda platform: win-64 dependencies: - aws-c-auth: '>=0.10.4,<0.10.5.0a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-auth: '>=0.10.3,<0.10.4.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' aws-c-http: '>=0.11.0,<0.11.1.0a0' - aws-c-io: '>=0.27.3,<0.27.4.0a0' - aws-c-s3: '>=0.12.8,<0.12.9.0a0' + aws-c-io: '>=0.26.3,<0.26.4.0a0' + aws-c-s3: '>=0.12.6,<0.12.7.0a0' aws-c-sdkutils: '>=0.2.7,<0.2.8.0a0' libaec: '>=1.1.5,<2.0a0' libcurl: '>=8.21.0,<9.0a0' @@ -2052,10 +2052,10 @@ package: ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/hdf5-2.1.0-nompi_h0d3e4b2_110.conda + url: https://repo.prefix.dev/conda-forge/win-64/hdf5-2.1.0-nompi_h0065017_109.conda hash: - md5: 146134e4db96613ecdc63cf44bec847d - sha256: f6c79581d03d2e2e0cbbb0391d21a149a1f9c311159a9f74c6be8a79e2f696d9 + md5: 174cdaf7ae4c6c6f1f0b085266aeee58 + sha256: c30f0c1d941b8e820c62652343f53ba63c09aef7f7587382cdc7637d1a4044a2 category: main optional: false - name: hpack @@ -2194,10 +2194,10 @@ package: ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/icu-78.3-h637d24d_1.conda + url: https://repo.prefix.dev/conda-forge/win-64/icu-78.3-h637d24d_0.conda hash: - md5: e3232ea6dafd9f12663f860a3194d6dc - sha256: 61f16ae57ee8956d5c1f69e703f7abaebb972d0def1edc5704d7198430398295 + md5: 0097b24800cb696915c3dbd1f5335d3f + sha256: 1bda728d70a619731b278c859eda364146cb5b4b8c739a64da8128353d81d1c4 category: main optional: false - name: idna @@ -2795,7 +2795,7 @@ package: category: dev optional: true - name: jupyter-builder - version: 1.1.1 + version: 1.1.0 manager: conda platform: linux-64 dependencies: @@ -2803,14 +2803,14 @@ package: python: '' tomli: '' traitlets: '' - url: https://repo.prefix.dev/conda-forge/noarch/jupyter-builder-1.1.1-pyhcf101f3_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/jupyter-builder-1.1.0-pyhcf101f3_0.conda hash: - md5: 3595d544a64a3225810a71e402aba766 - sha256: 876ead0b5381a732b4d3100b6494866d88d969ff428010d1e30f8350193dbf86 + md5: 67e33322de030b8b1852ee883002ec5b + sha256: 743f6ae17a6f6e37860958fa00db96bdec5c011bcef8b166bb8c84f3c957f7dc category: dev optional: true - name: jupyter-builder - version: 1.1.1 + version: 1.1.0 manager: conda platform: win-64 dependencies: @@ -2818,10 +2818,10 @@ package: python: '' tomli: '' traitlets: '' - url: https://repo.prefix.dev/conda-forge/noarch/jupyter-builder-1.1.1-pyhcf101f3_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/jupyter-builder-1.1.0-pyhcf101f3_0.conda hash: - md5: 3595d544a64a3225810a71e402aba766 - sha256: 876ead0b5381a732b4d3100b6494866d88d969ff428010d1e30f8350193dbf86 + md5: 67e33322de030b8b1852ee883002ec5b + sha256: 743f6ae17a6f6e37860958fa00db96bdec5c011bcef8b166bb8c84f3c957f7dc category: dev optional: true - name: jupyter-lsp @@ -3046,7 +3046,7 @@ package: category: dev optional: true - name: jupyterlab - version: 4.6.2 + version: 4.6.1 manager: conda platform: linux-64 dependencies: @@ -3065,15 +3065,14 @@ package: tomli: '>=1.2.2' tornado: '>=6.2.0' traitlets: '' - typing_extensions: '>=4.4.0' - url: https://repo.prefix.dev/conda-forge/noarch/jupyterlab-4.6.2-pyhd8ed1ab_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/jupyterlab-4.6.1-pyhd8ed1ab_0.conda hash: - md5: 74e2ef595d91aaffcf24815b4865bbc0 - sha256: a65dfe3aa15281377d3a589f0e86c463e6d8261b481bc892f7a40566ab1c675c + md5: 7ba07211c94d434f3223a4418f8b1ff8 + sha256: ad4a462da846fb8e9feebe39170553fc6a94252f4d4cca8efaf0dfeefa907099 category: dev optional: true - name: jupyterlab - version: 4.6.2 + version: 4.6.1 manager: conda platform: win-64 dependencies: @@ -3092,11 +3091,10 @@ package: tomli: '>=1.2.2' tornado: '>=6.2.0' traitlets: '' - typing_extensions: '>=4.4.0' - url: https://repo.prefix.dev/conda-forge/noarch/jupyterlab-4.6.2-pyhd8ed1ab_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/jupyterlab-4.6.1-pyhd8ed1ab_0.conda hash: - md5: 74e2ef595d91aaffcf24815b4865bbc0 - sha256: a65dfe3aa15281377d3a589f0e86c463e6d8261b481bc892f7a40566ab1c675c + md5: 7ba07211c94d434f3223a4418f8b1ff8 + sha256: ad4a462da846fb8e9feebe39170553fc6a94252f4d4cca8efaf0dfeefa907099 category: dev optional: true - name: jupyterlab_pygments @@ -3188,7 +3186,7 @@ package: category: dev optional: true - name: jupytext - version: 1.19.5 + version: 1.19.4 manager: conda platform: linux-64 dependencies: @@ -3199,14 +3197,14 @@ package: python: '>=3.10' pyyaml: '' tomli: '' - url: https://repo.prefix.dev/conda-forge/noarch/jupytext-1.19.5-pyh0398c0e_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/jupytext-1.19.4-pyh0398c0e_0.conda hash: - md5: 351c462ba9353930e745d620e6ceb09e - sha256: 1b0a1b68ee3d57dba85bdedfffd562b840b6914fd37afdf36f8f174c0d490b49 + md5: 9211ca1ee636d22fcec6df8fef0ce4e8 + sha256: 8aa4d40d6e2d4339067c36d30f29b4c7c16d14277fe4f581a3d605e5217314a8 category: dev optional: true - name: jupytext - version: 1.19.5 + version: 1.19.4 manager: conda platform: win-64 dependencies: @@ -3217,10 +3215,10 @@ package: python: '>=3.10' pyyaml: '' tomli: '' - url: https://repo.prefix.dev/conda-forge/noarch/jupytext-1.19.5-pyh0398c0e_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/jupytext-1.19.4-pyh0398c0e_0.conda hash: - md5: 351c462ba9353930e745d620e6ceb09e - sha256: 1b0a1b68ee3d57dba85bdedfffd562b840b6914fd37afdf36f8f174c0d490b49 + md5: 9211ca1ee636d22fcec6df8fef0ce4e8 + sha256: 8aa4d40d6e2d4339067c36d30f29b4c7c16d14277fe4f581a3d605e5217314a8 category: dev optional: true - name: keyutils @@ -3356,16 +3354,16 @@ package: category: main optional: false - name: ld_impl_linux-64 - version: 2.46.1 + version: 2.45.1 manager: conda platform: linux-64 dependencies: __glibc: '>=2.17,<3.0.a0' zstd: '>=1.5.7,<1.6.0a0' - url: https://repo.prefix.dev/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_102.conda + url: https://repo.prefix.dev/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda hash: - md5: 449500f2c089da11c40f5c21312e3e07 - sha256: 27d83f1188cd19bcb7754a078b3fa7f4cfb8527f8eb2fde54dd01fc529d1adec + md5: 18335a698559cdbcd86150a48bf54ba6 + sha256: 3d584956604909ff5df353767f3a2a2f60e07d070b328d109f30ac40cd62df6c category: main optional: false - name: lerc @@ -4757,29 +4755,29 @@ package: category: main optional: false - name: mistune - version: 3.3.4 + version: 3.3.3 manager: conda platform: linux-64 dependencies: python: '' typing_extensions: '' - url: https://repo.prefix.dev/conda-forge/noarch/mistune-3.3.4-pyhcf101f3_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/mistune-3.3.3-pyhcf101f3_0.conda hash: - md5: 1c0ebec41ef9214160da1ee6a0880fce - sha256: 306af633cb4ac85d04024d7c243ac2031c488bf5b0912207e7304c27a5d65a85 + md5: 6bda0cbb7b7608101bc2c49ee6f38de7 + sha256: 2e16a4297761697ed97f4c9974f5c350c7dbca8877a582c7793f4295db6c00a2 category: dev optional: true - name: mistune - version: 3.3.4 + version: 3.3.3 manager: conda platform: win-64 dependencies: python: '' typing_extensions: '' - url: https://repo.prefix.dev/conda-forge/noarch/mistune-3.3.4-pyhcf101f3_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/mistune-3.3.3-pyhcf101f3_0.conda hash: - md5: 1c0ebec41ef9214160da1ee6a0880fce - sha256: 306af633cb4ac85d04024d7c243ac2031c488bf5b0912207e7304c27a5d65a85 + md5: 6bda0cbb7b7608101bc2c49ee6f38de7 + sha256: 2e16a4297761697ed97f4c9974f5c350c7dbca8877a582c7793f4295db6c00a2 category: dev optional: true - name: mkl @@ -5160,39 +5158,39 @@ package: category: dev optional: true - name: notebook - version: 7.6.1 + version: 7.6.0 manager: conda platform: linux-64 dependencies: jupyter-builder: '>=1.0.2,<2' jupyter_server: '>=2.19.0,<3' - jupyterlab: '>=4.6.2,<4.7' + jupyterlab: '>=4.6.0,<4.7' jupyterlab_server: '>=2.28.0,<3' notebook-shim: '>=0.2,<0.3' python: '' tornado: '>=6.2.0' - url: https://repo.prefix.dev/conda-forge/noarch/notebook-7.6.1-pyhcf101f3_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/notebook-7.6.0-pyhcf101f3_0.conda hash: - md5: 5ae09febf4b5923fe454019c1b49ba78 - sha256: 5e4436013e83e7fb11d6a5c85a458a6e18364df6d0f50ee22dda534d1d5e6431 + md5: d9eb17006787e506bcb70f16daf1b1c1 + sha256: 5b48c26a966caae81169cc8056321a4c139103880eb01e1f4f02ad6ff9390421 category: dev optional: true - name: notebook - version: 7.6.1 + version: 7.6.0 manager: conda platform: win-64 dependencies: jupyter-builder: '>=1.0.2,<2' jupyter_server: '>=2.19.0,<3' - jupyterlab: '>=4.6.2,<4.7' + jupyterlab: '>=4.6.0,<4.7' jupyterlab_server: '>=2.28.0,<3' notebook-shim: '>=0.2,<0.3' python: '' tornado: '>=6.2.0' - url: https://repo.prefix.dev/conda-forge/noarch/notebook-7.6.1-pyhcf101f3_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/notebook-7.6.0-pyhcf101f3_0.conda hash: - md5: 5ae09febf4b5923fe454019c1b49ba78 - sha256: 5e4436013e83e7fb11d6a5c85a458a6e18364df6d0f50ee22dda534d1d5e6431 + md5: d9eb17006787e506bcb70f16daf1b1c1 + sha256: 5b48c26a966caae81169cc8056321a4c139103880eb01e1f4f02ad6ff9390421 category: dev optional: true - name: notebook-shim @@ -5697,27 +5695,27 @@ package: category: main optional: false - name: platformdirs - version: 4.11.0 + version: 4.10.0 manager: conda platform: linux-64 dependencies: python: '' - url: https://repo.prefix.dev/conda-forge/noarch/platformdirs-4.11.0-pyhcf101f3_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/platformdirs-4.10.0-pyhcf101f3_0.conda hash: - md5: 1fadaa6dd1d03d062075f84157ac2cc7 - sha256: a4b8c7d3b3703d10f93187986d59aec09b22033978945845899beb7f849a7be6 + md5: 2c5ef45db85d34799771629bd5860fd7 + sha256: 9e5e1fd3506ccfc4d444fc4d2d39b0ed097d5d0e3bd3d4bdf6bcc81aaf66860d category: dev optional: true - name: platformdirs - version: 4.11.0 + version: 4.10.0 manager: conda platform: win-64 dependencies: python: '' - url: https://repo.prefix.dev/conda-forge/noarch/platformdirs-4.11.0-pyhcf101f3_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/platformdirs-4.10.0-pyhcf101f3_0.conda hash: - md5: 1fadaa6dd1d03d062075f84157ac2cc7 - sha256: a4b8c7d3b3703d10f93187986d59aec09b22033978945845899beb7f849a7be6 + md5: 2c5ef45db85d34799771629bd5860fd7 + sha256: 9e5e1fd3506ccfc4d444fc4d2d39b0ed097d5d0e3bd3d4bdf6bcc81aaf66860d category: dev optional: true - name: pluggy @@ -6250,6 +6248,7 @@ package: libzlib: '>=1.3.1,<2.0a0' ncurses: '>=6.5,<7.0a0' openssl: '>=3.5.5,<4.0a0' + pip: '' readline: '>=8.3,<9.0a0' tk: '>=8.6.13,<8.7.0a0' tzdata: '' @@ -6271,6 +6270,7 @@ package: libsqlite: '>=3.51.2,<4.0a0' libzlib: '>=1.3.1,<2.0a0' openssl: '>=3.5.5,<4.0a0' + pip: '' tk: '>=8.6.13,<8.7.0a0' tzdata: '' ucrt: '>=10.0.20348.0' @@ -7105,27 +7105,27 @@ package: category: main optional: false - name: soupsieve - version: 2.9.1 + version: 2.8.4 manager: conda platform: linux-64 dependencies: python: '>=3.10' - url: https://repo.prefix.dev/conda-forge/noarch/soupsieve-2.9.1-pyhd8ed1ab_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/soupsieve-2.8.4-pyhd8ed1ab_0.conda hash: - md5: 0fc47d7f5a6dbafd186a8f92cad5de3f - sha256: d10bfe45ffd6fa37baef69d852f9fa882be4e6fe4cdd7286f3543e79b803cadb + md5: 9e21f087f087f805debe877d88e00a14 + sha256: 2afa5fe9331c09b4c4689ddf6ace8fc16c837eae547c57dab325b844072fdd77 category: dev optional: true - name: soupsieve - version: 2.9.1 + version: 2.8.4 manager: conda platform: win-64 dependencies: python: '>=3.10' - url: https://repo.prefix.dev/conda-forge/noarch/soupsieve-2.9.1-pyhd8ed1ab_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/soupsieve-2.8.4-pyhd8ed1ab_0.conda hash: - md5: 0fc47d7f5a6dbafd186a8f92cad5de3f - sha256: d10bfe45ffd6fa37baef69d852f9fa882be4e6fe4cdd7286f3543e79b803cadb + md5: 9e21f087f087f805debe877d88e00a14 + sha256: 2afa5fe9331c09b4c4689ddf6ace8fc16c837eae547c57dab325b844072fdd77 category: dev optional: true - name: sphinx @@ -7511,11 +7511,11 @@ package: dependencies: __glibc: '>=2.17,<3.0.a0' libgcc: '>=14' - libzlib: '>=1.3.2,<2.0a0' - url: https://repo.prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_hd70dff1_3.conda + libzlib: '>=1.3.1,<2.0a0' + url: https://repo.prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda hash: - md5: 48a1049e710857572fc2a832aa394d9f - sha256: 43624eab22f5f29df7d6ffe914cf442f28fd559b55b290906255492826e636e8 + md5: cffd3bdd58090148f4cfcd831f4b26ab + sha256: cafeec44494f842ffeca27e9c8b0c27ed714f93ac77ddadc6aaf726b5554ebac category: main optional: false - name: tk @@ -7526,10 +7526,10 @@ package: ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/tk-8.6.13-h967ab96_3.conda + url: https://repo.prefix.dev/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda hash: - md5: aaf79e2af50a151fb5b5a3e3f38b7a69 - sha256: 13fa29257d43f8e630a1e591ed77fae9bbbb236b011432f01e2034cf36e6bf03 + md5: 0481bfd9814bf525bd4b3ee4b51494c4 + sha256: 0e79810fae28f3b69fe7391b0d43f5474d6bd91d451d5f2bde02f55ae481d5e3 category: main optional: false - name: tomli @@ -7557,27 +7557,27 @@ package: category: dev optional: true - name: tomlkit - version: 0.15.1 + version: 0.15.0 manager: conda platform: linux-64 dependencies: - python: '' - url: https://repo.prefix.dev/conda-forge/noarch/tomlkit-0.15.1-pyhcf101f3_0.conda + python: '>=3.10' + url: https://repo.prefix.dev/conda-forge/noarch/tomlkit-0.15.0-pyha770c72_0.conda hash: - md5: e4942e2de7436ff28be7f5645cf3c8d6 - sha256: 5cf833b4d199688b7652fb322ecc134de9555e9444d9249750de9a153421ad0a + md5: 42ef10a8f7f5d55a2e267c0d5daa6387 + sha256: 1cd52f9ccb4854c4d731438afe0e833b6b71edaf5ede661152aa98efb3a7cc70 category: dev optional: true - name: tomlkit - version: 0.15.1 + version: 0.15.0 manager: conda platform: win-64 dependencies: - python: '' - url: https://repo.prefix.dev/conda-forge/noarch/tomlkit-0.15.1-pyhcf101f3_0.conda + python: '>=3.10' + url: https://repo.prefix.dev/conda-forge/noarch/tomlkit-0.15.0-pyha770c72_0.conda hash: - md5: e4942e2de7436ff28be7f5645cf3c8d6 - sha256: 5cf833b4d199688b7652fb322ecc134de9555e9444d9249750de9a153421ad0a + md5: 42ef10a8f7f5d55a2e267c0d5daa6387 + sha256: 1cd52f9ccb4854c4d731438afe0e833b6b71edaf5ede661152aa98efb3a7cc70 category: dev optional: true - name: toolz @@ -7636,30 +7636,30 @@ package: category: main optional: false - name: tqdm - version: 4.69.0 + version: 4.68.4 manager: conda platform: linux-64 dependencies: __unix: '' python: '' - url: https://repo.prefix.dev/conda-forge/noarch/tqdm-4.69.0-pyh8f84b5b_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/tqdm-4.68.4-pyh8f84b5b_0.conda hash: - md5: c0f814b31ce7e2d0d5b5756156ebe144 - sha256: d1ec99b77153728a956a9be0d6767969bf0126869ad76f71dc166c2601ad82ad + md5: 54772f97dc361b1659ef0b34d71d39b4 + sha256: 1964320e89c00a531705449187f4b8ed85f935c73f13ba55de3a6b35b05443fa category: main optional: false - name: tqdm - version: 4.69.0 + version: 4.68.4 manager: conda platform: win-64 dependencies: __win: '' colorama: '' python: '' - url: https://repo.prefix.dev/conda-forge/noarch/tqdm-4.69.0-pyha7b4d00_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/tqdm-4.68.4-pyha7b4d00_0.conda hash: - md5: 4eb25e44c870dfbdd9bb34e29f8945f6 - sha256: fad3a696f659aab91c62f2c19b3172c7acf558acd9f180fc49955bffe22398cf + md5: a4708e48840dbcca9cb9c4344226080c + sha256: fc8b4e3ec4e8d8f0b0bd399514e70141e0b1726f2ec58b6439158f6a20c02a23 category: main optional: false - name: traitlets @@ -7811,25 +7811,25 @@ package: category: dev optional: true - name: tzdata - version: 2026c + version: 2025c manager: conda platform: linux-64 dependencies: {} - url: https://repo.prefix.dev/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda hash: - md5: fcb489df604d100968b737f2cb6076c6 - sha256: b928c30ddcb0e3f544c6eade8352737e6e610e263276b90232db6a578ef899d8 + md5: ad659d0a2b3e47e38d829aa8cad2d610 + sha256: 1d30098909076af33a35017eed6f2953af1c769e273a0626a04722ac4acaba3c category: main optional: false - name: tzdata - version: 2026c + version: 2025c manager: conda platform: win-64 dependencies: {} - url: https://repo.prefix.dev/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda hash: - md5: fcb489df604d100968b737f2cb6076c6 - sha256: b928c30ddcb0e3f544c6eade8352737e6e610e263276b90232db6a578ef899d8 + md5: ad659d0a2b3e47e38d829aa8cad2d610 + sha256: 1d30098909076af33a35017eed6f2953af1c769e273a0626a04722ac4acaba3c category: main optional: false - name: ucrt @@ -8472,105 +8472,121 @@ package: category: main optional: false - name: geoapps-utils - version: 0.7.0 + version: 0.7.1.dev36+2deafcc manager: pip platform: linux-64 dependencies: - geoh5py: '>=0.13.0b1,<0.14.dev0' + geoh5py: 0.13.1.dev114+3722011a matplotlib: '>=3.10.0,<3.11.0' numpy: '>=2.4.2,<2.5.0' pydantic: '>=2.12.0,<2.13.0' scipy: '>=1.17.0,<1.18.0' - url: https://files.pythonhosted.org/packages/97/98/2f55e08a24ec14e493d8f83d425e353512f0800bd6d36cd62bea0169fff0/geoapps_utils-0.7.0-py3-none-any.whl + url: git+https://github.com/MiraGeoscience/geoapps-utils.git@2deafccc38a40039dc6b3f376bf0e3c9d0371498 hash: - sha256: 97796a9a1dbaed392eb2be97a1e66f2ae894b64460bdb940f42a945e31e2c417 + sha256: 2deafccc38a40039dc6b3f376bf0e3c9d0371498 + source: + type: url + url: git+https://github.com/MiraGeoscience/geoapps-utils.git@2deafccc38a40039dc6b3f376bf0e3c9d0371498 category: main optional: false - name: geoapps-utils - version: 0.7.0 + version: 0.7.1.dev36+2deafcc manager: pip platform: win-64 dependencies: - geoh5py: '>=0.13.0b1,<0.14.dev0' + geoh5py: 0.13.1.dev114+3722011a matplotlib: '>=3.10.0,<3.11.0' numpy: '>=2.4.2,<2.5.0' pydantic: '>=2.12.0,<2.13.0' scipy: '>=1.17.0,<1.18.0' - url: https://files.pythonhosted.org/packages/97/98/2f55e08a24ec14e493d8f83d425e353512f0800bd6d36cd62bea0169fff0/geoapps_utils-0.7.0-py3-none-any.whl + url: git+https://github.com/MiraGeoscience/geoapps-utils.git@2deafccc38a40039dc6b3f376bf0e3c9d0371498 hash: - sha256: 97796a9a1dbaed392eb2be97a1e66f2ae894b64460bdb940f42a945e31e2c417 + sha256: 2deafccc38a40039dc6b3f376bf0e3c9d0371498 + source: + type: url + url: git+https://github.com/MiraGeoscience/geoapps-utils.git@2deafccc38a40039dc6b3f376bf0e3c9d0371498 category: main optional: false - name: geoh5py - version: 0.13.1rc2 + version: 0.13.1.dev114+3722011a manager: pip platform: linux-64 dependencies: h5py: '>=3.15.0,<3.16.0' numpy: '>=2.4.0,<2.5.0' - pillow: '>=12.2.0,<13.0.0' + pillow: '>=12.2.0,<12.3.0' psutil: '>=7.2.2,<7.3.0' pydantic: '>=2.12.0,<2.13.0' - url: https://files.pythonhosted.org/packages/dc/74/001da71889b7f9bd1f6e68bde1bcac0fd84da998f2deced929bf01ff62ef/geoh5py-0.13.1rc2-py3-none-any.whl + url: git+https://github.com/MiraGeoscience/geoh5py.git@3722011aa9fd8512f1bd6a3e58e2220a88ca8ce2 hash: - sha256: 3a01ce48994bad4bd2ee0f7f97eb8bdf6bba490b11d9cbbaac12f1a0f300e389 + sha256: 3722011aa9fd8512f1bd6a3e58e2220a88ca8ce2 + source: + type: url + url: git+https://github.com/MiraGeoscience/geoh5py.git@3722011aa9fd8512f1bd6a3e58e2220a88ca8ce2 category: main optional: false - name: geoh5py - version: 0.13.1rc2 + version: 0.13.1.dev114+3722011a manager: pip platform: win-64 dependencies: h5py: '>=3.15.0,<3.16.0' numpy: '>=2.4.0,<2.5.0' - pillow: '>=12.2.0,<13.0.0' + pillow: '>=12.2.0,<12.3.0' psutil: '>=7.2.2,<7.3.0' pydantic: '>=2.12.0,<2.13.0' - url: https://files.pythonhosted.org/packages/dc/74/001da71889b7f9bd1f6e68bde1bcac0fd84da998f2deced929bf01ff62ef/geoh5py-0.13.1rc2-py3-none-any.whl + url: git+https://github.com/MiraGeoscience/geoh5py.git@3722011aa9fd8512f1bd6a3e58e2220a88ca8ce2 hash: - sha256: 3a01ce48994bad4bd2ee0f7f97eb8bdf6bba490b11d9cbbaac12f1a0f300e389 + sha256: 3722011aa9fd8512f1bd6a3e58e2220a88ca8ce2 + source: + type: url + url: git+https://github.com/MiraGeoscience/geoh5py.git@3722011aa9fd8512f1bd6a3e58e2220a88ca8ce2 category: main optional: false - name: grid-apps - version: 0.2.0 + version: 0.2.0b3.dev38+7bc33d1 manager: pip platform: linux-64 dependencies: discretize: '>=0.12.0,<0.13.0' - geoapps-utils: '>=0.7.0b1,<0.8.dev0' - geoh5py: '>=0.13.0b1,<0.14.dev0' + geoapps-utils: 0.7.1.dev36+2deafcc + geoh5py: 0.13.1.dev114+3722011a numpy: '>=2.4.2,<2.5.0' pydantic: '>=2.12.0,<2.13.0' scipy: '>=1.17.0,<1.18.0' - url: https://files.pythonhosted.org/packages/79/05/70c54f61dcab22dea9f0d16f2218c67ce8e4174ce70354db129244379a7e/grid_apps-0.2.0-py3-none-any.whl + url: git+https://github.com/MiraGeoscience/grid-apps.git@7bc33d170f03dd6da15ee531b49cdb5835b36ea4 hash: - sha256: 77dd1881c3467687f7b1698eefc285368f0f84a1b154651e97006b36a668fa54 + sha256: 7bc33d170f03dd6da15ee531b49cdb5835b36ea4 + source: + type: url + url: git+https://github.com/MiraGeoscience/grid-apps.git@7bc33d170f03dd6da15ee531b49cdb5835b36ea4 category: main optional: false - name: grid-apps - version: 0.2.0 + version: 0.2.0b3.dev38+7bc33d1 manager: pip platform: win-64 dependencies: discretize: '>=0.12.0,<0.13.0' - geoapps-utils: '>=0.7.0b1,<0.8.dev0' - geoh5py: '>=0.13.0b1,<0.14.dev0' + geoapps-utils: 0.7.1.dev36+2deafcc + geoh5py: 0.13.1.dev114+3722011a numpy: '>=2.4.2,<2.5.0' pydantic: '>=2.12.0,<2.13.0' scipy: '>=1.17.0,<1.18.0' - url: https://files.pythonhosted.org/packages/79/05/70c54f61dcab22dea9f0d16f2218c67ce8e4174ce70354db129244379a7e/grid_apps-0.2.0-py3-none-any.whl + url: git+https://github.com/MiraGeoscience/grid-apps.git@7bc33d170f03dd6da15ee531b49cdb5835b36ea4 hash: - sha256: 77dd1881c3467687f7b1698eefc285368f0f84a1b154651e97006b36a668fa54 + sha256: 7bc33d170f03dd6da15ee531b49cdb5835b36ea4 + source: + type: url + url: git+https://github.com/MiraGeoscience/grid-apps.git@7bc33d170f03dd6da15ee531b49cdb5835b36ea4 category: main optional: false - name: mira-simpeg - version: 0.25.0.1 + version: 0.25.0.2.dev11+g87230755b manager: pip platform: linux-64 dependencies: - dask: '*' discretize: '>=0.11' - fsspec: '>=0.3.3' geoana: '>=0.7.0' geoh5py: '>=0.13.0b1' libdlf: '*' @@ -8579,20 +8595,20 @@ package: pymatsolver: '>=0.3' scipy: '>=1.8' typing-extensions: '*' - zarr: '*' - url: https://files.pythonhosted.org/packages/c4/f7/f7a5357a165f52e2de83878d20cbf236cf6f5348470008140afd7fdde6ba/mira_simpeg-0.25.0.1-py3-none-any.whl + url: git+https://github.com/MiraGeoscience/simpeg.git@87230755b40f3961fd9b2383df191c140a29c601 hash: - sha256: 8bceaaf017df95ea925e20feaa2e6a5dc0f8fede434738d6493c775cb5341b38 + sha256: 87230755b40f3961fd9b2383df191c140a29c601 + source: + type: url + url: git+https://github.com/MiraGeoscience/simpeg.git@87230755b40f3961fd9b2383df191c140a29c601 category: main optional: false - name: mira-simpeg - version: 0.25.0.1 + version: 0.25.0.2.dev11+g87230755b manager: pip platform: win-64 dependencies: - dask: '*' discretize: '>=0.11' - fsspec: '>=0.3.3' geoana: '>=0.7.0' geoh5py: '>=0.13.0b1' libdlf: '*' @@ -8601,9 +8617,11 @@ package: pymatsolver: '>=0.3' scipy: '>=1.8' typing-extensions: '*' - zarr: '*' - url: https://files.pythonhosted.org/packages/c4/f7/f7a5357a165f52e2de83878d20cbf236cf6f5348470008140afd7fdde6ba/mira_simpeg-0.25.0.1-py3-none-any.whl + url: git+https://github.com/MiraGeoscience/simpeg.git@87230755b40f3961fd9b2383df191c140a29c601 hash: - sha256: 8bceaaf017df95ea925e20feaa2e6a5dc0f8fede434738d6493c775cb5341b38 + sha256: 87230755b40f3961fd9b2383df191c140a29c601 + source: + type: url + url: git+https://github.com/MiraGeoscience/simpeg.git@87230755b40f3961fd9b2383df191c140a29c601 category: main optional: false diff --git a/py-3.13.conda-lock.yml b/py-3.13.conda-lock.yml index 46f0d4d3c..330cd7b8a 100644 --- a/py-3.13.conda-lock.yml +++ b/py-3.13.conda-lock.yml @@ -15,8 +15,8 @@ version: 1 metadata: content_hash: - win-64: 9b56450802b028b33a88fec3ccdb330045ac53ddb06ca386fd71b21c5676fe48 - linux-64: e47fe9019cd75c24e0ceebabf988abf3e89f41557900def026b5fa36554bf11d + win-64: d81085ff8b943e3347da9c2174e4427cd5736c1d273bc4a6b4c77ebac25efae5 + linux-64: 46e87a977a45c0b272bd0321b439febe2f11e1e69a0ddc4e780729ddb9a22d9c channels: - url: conda-forge used_env_vars: [] @@ -61,10 +61,10 @@ package: dependencies: cpython: '' python-gil: '' - url: https://repo.prefix.dev/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda + url: https://repo.prefix.dev/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda hash: - md5: 3845f3d75991bae0fb90884662f4327c - sha256: 2a7204314663eeda5dec482a956f0e2eaf289bd5b9953eaaaad0e81aa64638f2 + md5: aaa2a381ccc56eac91d63b6c1240312f + sha256: a3967b937b9abf0f2a99f3173fa4630293979bd1644709d89580e7c62a544661 category: dev optional: true - name: _python_abi3_support @@ -74,10 +74,10 @@ package: dependencies: cpython: '' python-gil: '' - url: https://repo.prefix.dev/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda + url: https://repo.prefix.dev/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda hash: - md5: 3845f3d75991bae0fb90884662f4327c - sha256: 2a7204314663eeda5dec482a956f0e2eaf289bd5b9953eaaaad0e81aa64638f2 + md5: aaa2a381ccc56eac91d63b6c1240312f + sha256: a3967b937b9abf0f2a99f3173fa4630293979bd1644709d89580e7c62a544661 category: dev optional: true - name: alabaster @@ -300,27 +300,27 @@ package: category: dev optional: true - name: asttokens - version: 3.0.2 + version: 3.0.1 manager: conda platform: linux-64 dependencies: python: '>=3.10' - url: https://repo.prefix.dev/conda-forge/noarch/asttokens-3.0.2-pyhd8ed1ab_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda hash: - md5: 89d495168582cb00428dad699d149624 - sha256: fbbd8ce60cbd5c16f3fe559eb644551f94285caff30985ea961ff851c1cf25ac + md5: 9673a61a297b00016442e022d689faa6 + sha256: ee4da0f3fe9d59439798ee399ef3e482791e48784873d546e706d0935f9ff010 category: dev optional: true - name: asttokens - version: 3.0.2 + version: 3.0.1 manager: conda platform: win-64 dependencies: python: '>=3.10' - url: https://repo.prefix.dev/conda-forge/noarch/asttokens-3.0.2-pyhd8ed1ab_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda hash: - md5: 89d495168582cb00428dad699d149624 - sha256: fbbd8ce60cbd5c16f3fe559eb644551f94285caff30985ea961ff851c1cf25ac + md5: 9673a61a297b00016442e022d689faa6 + sha256: ee4da0f3fe9d59439798ee399ef3e482791e48784873d546e706d0935f9ff010 category: dev optional: true - name: async-lru @@ -374,40 +374,40 @@ package: category: dev optional: true - name: aws-c-auth - version: 0.10.4 + version: 0.10.3 manager: conda platform: linux-64 dependencies: __glibc: '>=2.17,<3.0.a0' aws-c-cal: '>=0.9.14,<0.9.15.0a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' aws-c-http: '>=0.11.0,<0.11.1.0a0' - aws-c-io: '>=0.27.3,<0.27.4.0a0' + aws-c-io: '>=0.26.3,<0.26.4.0a0' aws-c-sdkutils: '>=0.2.7,<0.2.8.0a0' libgcc: '>=14' - url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-auth-0.10.4-hb7a77c6_1.conda + url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-auth-0.10.3-hdc83ae9_4.conda hash: - md5: 4347d5ffdf34adf9c5edd10837727d96 - sha256: 845fe32ee750d4a4d3efdb1d95a8c29c3388e3ea9787b77341ec4abe4e198b11 + md5: 00840a04f8ec0033ed380f150fcd1c93 + sha256: d7746767f17d3bf12cef8e69b7d6fa1ba975a25d4a1c3250bc7031020f0eb94d category: main optional: false - name: aws-c-auth - version: 0.10.4 + version: 0.10.3 manager: conda platform: win-64 dependencies: aws-c-cal: '>=0.9.14,<0.9.15.0a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' aws-c-http: '>=0.11.0,<0.11.1.0a0' - aws-c-io: '>=0.27.3,<0.27.4.0a0' + aws-c-io: '>=0.26.3,<0.26.4.0a0' aws-c-sdkutils: '>=0.2.7,<0.2.8.0a0' ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/aws-c-auth-0.10.4-hc6e9107_1.conda + url: https://repo.prefix.dev/conda-forge/win-64/aws-c-auth-0.10.3-h0a1c2aa_4.conda hash: - md5: f7069cdc81f7a5e781f1faf6aab6c171 - sha256: cf7ab8abaac6b462463310412cc37d087a767a95920809cbe6dc0a08fe4bcfbd + md5: f191830ba03429449e6de9ba5f00a1b7 + sha256: a87f863ffd39c69340fbda750eafde23097681e45bb08c04a8566e87b327a04a category: main optional: false - name: aws-c-cal @@ -416,13 +416,13 @@ package: platform: linux-64 dependencies: __glibc: '>=2.17,<3.0.a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' libgcc: '>=14' openssl: '>=3.5.7,<4.0a0' - url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-cal-0.9.14-h2aa3ae6_4.conda + url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-cal-0.9.14-h6e18c7b_3.conda hash: - md5: 9c6072e7b882d35ac956c07e493d6a9e - sha256: a67c944be1728f576c02fe8f28b0cbb78b5353415075db3da4ef71bc9dd8c00c + md5: d1b68989381f8cbf582ba7a5e3bb5b02 + sha256: 594d8c27ea57997863499a8e55ac8fb6587cdd62639445da91e1a88756488a88 category: main optional: false - name: aws-c-cal @@ -430,41 +430,41 @@ package: manager: conda platform: win-64 dependencies: - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/aws-c-cal-0.9.14-h032d170_4.conda + url: https://repo.prefix.dev/conda-forge/win-64/aws-c-cal-0.9.14-h92e57be_3.conda hash: - md5: fda8fa85ef5d8570d5a0aadea688bb82 - sha256: cda33360c71ab9a4b4e269004a5672d712fa1ae581ecca0404181805ce6762ce + md5: 5057d17412a615efce912b41c6a26d82 + sha256: dd706664dd7ab021320634481f5b126f774c6b0526eab24b79f9a6a3961ea6ed category: main optional: false - name: aws-c-common - version: 0.14.2 + version: 0.14.1 manager: conda platform: linux-64 dependencies: __glibc: '>=2.17,<3.0.a0' libgcc: '>=14' - url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-common-0.14.2-hb03c661_0.conda + url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-common-0.14.1-hb03c661_0.conda hash: - md5: f3e0b2e044485ae90d4a59c77e7a0182 - sha256: 701398f1c9978c41e93cb0947869a66b7f8004e9d6e548d63d11aedae83cfb02 + md5: 4faa1e8d0cbc9137e8b0109c78931234 + sha256: a83c1a18754fe0f466d3a230106d6a52847c915c625b0be6b9f4677f46024a6c category: main optional: false - name: aws-c-common - version: 0.14.2 + version: 0.14.1 manager: conda platform: win-64 dependencies: ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/aws-c-common-0.14.2-hfd05255_0.conda + url: https://repo.prefix.dev/conda-forge/win-64/aws-c-common-0.14.1-hfd05255_0.conda hash: - md5: 75b4445fec6477b271920f87830d22a3 - sha256: 63fc3f34a3b3d21d5f6527ba5136e132f3ad50541d4d16e385d03f4e47e1db2b + md5: bda6f415d6bf1be6c3de28477c3c6d21 + sha256: 803a59e9d4b324dc49aa9bc25f601f0766adbe344acf345015a1437210d9157e category: main optional: false - name: aws-c-compression @@ -473,12 +473,12 @@ package: platform: linux-64 dependencies: __glibc: '>=2.17,<3.0.a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' libgcc: '>=14' - url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-compression-0.3.2-h720e601_4.conda + url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-compression-0.3.2-haaf8e00_3.conda hash: - md5: c9be3f5854f349ae77a1a174b59e11a8 - sha256: 8e0a5513cfc42df8bd16adbd8e83a37d3ca89b8e04cb20eb04eb177bbbfea365 + md5: 953cf7d978432cc1703395b3e97b2776 + sha256: 5f40faa0c3661a231e1a01e4abf287621509ae60fb95c92fdafdf7a2dd3d0da7 category: main optional: false - name: aws-c-compression @@ -486,14 +486,14 @@ package: manager: conda platform: win-64 dependencies: - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/aws-c-compression-0.3.2-h1da161f_4.conda + url: https://repo.prefix.dev/conda-forge/win-64/aws-c-compression-0.3.2-h4c63076_3.conda hash: - md5: 996e5c7da8f9e255eac094d2413b40c3 - sha256: 97eaf03572b61d118616e8ee8459823c7b0f5dc26295bbab3ac3f6d671f1547a + md5: 5ee3f4b0f44948e8381d3ed85b968c6f + sha256: a41fdc98f48defd807d9befeec975cb772ceeb1ab1d944ecb7d73f2f13882d89 category: main optional: false - name: aws-c-http @@ -503,14 +503,14 @@ package: dependencies: __glibc: '>=2.17,<3.0.a0' aws-c-cal: '>=0.9.14,<0.9.15.0a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' aws-c-compression: '>=0.3.2,<0.3.3.0a0' - aws-c-io: '>=0.27.3,<0.27.4.0a0' + aws-c-io: '>=0.26.3,<0.26.4.0a0' libgcc: '>=14' - url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-http-0.11.0-h38ae05a_4.conda + url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-http-0.11.0-h434eaf2_3.conda hash: - md5: 97f2799ae6ff7b6f52dfa321fef9676b - sha256: 4b939b4a76a11c9ec50c891ae9bf232da8dcaf8797701df1e79ae51c988be2d4 + md5: 5d0c1e7ef42363ed72969ed488703644 + sha256: 8a21457233210ca69bae462d932d0513ccf6329a0914b2f454b42aa936ddf819 category: main optional: false - name: aws-c-http @@ -519,88 +519,88 @@ package: platform: win-64 dependencies: aws-c-cal: '>=0.9.14,<0.9.15.0a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' aws-c-compression: '>=0.3.2,<0.3.3.0a0' - aws-c-io: '>=0.27.3,<0.27.4.0a0' + aws-c-io: '>=0.26.3,<0.26.4.0a0' ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/aws-c-http-0.11.0-h667fc28_4.conda + url: https://repo.prefix.dev/conda-forge/win-64/aws-c-http-0.11.0-h2c07012_3.conda hash: - md5: c17a284b159959a8639298ed7da8ad0b - sha256: 304587d77daccde630fbdbb8ae2f3994cf583427f710c60d62e88bc97c4ff013 + md5: 7eb24c10ea05776b52dc4659dffd9a38 + sha256: 0957c39b445ab342bf0747334e4c823212c6e3297ac41f13d6bd96b221a51e0a category: main optional: false - name: aws-c-io - version: 0.27.3 + version: 0.26.3 manager: conda platform: linux-64 dependencies: __glibc: '>=2.17,<3.0.a0' aws-c-cal: '>=0.9.14,<0.9.15.0a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' libgcc: '>=14' s2n: '>=1.7.5,<1.7.6.0a0' - url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-io-0.27.3-h6f4d18d_1.conda + url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-io-0.26.3-ha0b0e19_6.conda hash: - md5: bd19b685b88557830f1a617a6d404eb2 - sha256: 94c32ca672ce290e250aea1cfb92582cca4658bdc3ec7cb1ceef254f34451595 + md5: bec59731db9cc0965f270a6e33a8db73 + sha256: 5406c95965698b7998f9288a05a5aa138d7cc6af82e4a823d3afaca09170e06c category: main optional: false - name: aws-c-io - version: 0.27.3 + version: 0.26.3 manager: conda platform: win-64 dependencies: aws-c-cal: '>=0.9.14,<0.9.15.0a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/aws-c-io-0.27.3-hbdc738f_1.conda + url: https://repo.prefix.dev/conda-forge/win-64/aws-c-io-0.26.3-hf70332f_6.conda hash: - md5: 579600368b15fb523d69e2a3f8a9bc55 - sha256: 36816ff394ee736fcf705db5b1c1491c98f6f77ef84791d73b71e78154df402d + md5: 7902637f56f67f42d298763e5d88a9e6 + sha256: 843e1d7f512adb0910c59ef0ae7772c87c8524878febfb793bf0106d813e82eb category: main optional: false - name: aws-c-s3 - version: 0.12.8 + version: 0.12.6 manager: conda platform: linux-64 dependencies: __glibc: '>=2.17,<3.0.a0' - aws-c-auth: '>=0.10.4,<0.10.5.0a0' + aws-c-auth: '>=0.10.3,<0.10.4.0a0' aws-c-cal: '>=0.9.14,<0.9.15.0a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' aws-c-http: '>=0.11.0,<0.11.1.0a0' - aws-c-io: '>=0.27.3,<0.27.4.0a0' + aws-c-io: '>=0.26.3,<0.26.4.0a0' aws-checksums: '>=0.2.10,<0.2.11.0a0' libgcc: '>=14' openssl: '>=3.5.7,<4.0a0' - url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-s3-0.12.8-h46fcd08_1.conda + url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-s3-0.12.6-hd767fb6_1.conda hash: - md5: 706aa99414d18db5b23475b45cc93a0b - sha256: a423bffbb0e6cacb5e2a0861b918ba3180e5132509afb1faf225ee9f0ec8f981 + md5: aa4756841efe742938984663ef2397ef + sha256: 81abe7f421c630b168454f40d4daf58d73b09aab78f8d99b5d6c8e2d132bcfc7 category: main optional: false - name: aws-c-s3 - version: 0.12.8 + version: 0.12.6 manager: conda platform: win-64 dependencies: - aws-c-auth: '>=0.10.4,<0.10.5.0a0' + aws-c-auth: '>=0.10.3,<0.10.4.0a0' aws-c-cal: '>=0.9.14,<0.9.15.0a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' aws-c-http: '>=0.11.0,<0.11.1.0a0' - aws-c-io: '>=0.27.3,<0.27.4.0a0' + aws-c-io: '>=0.26.3,<0.26.4.0a0' aws-checksums: '>=0.2.10,<0.2.11.0a0' ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/aws-c-s3-0.12.8-hd7ee1a1_1.conda + url: https://repo.prefix.dev/conda-forge/win-64/aws-c-s3-0.12.6-hcb9225d_1.conda hash: - md5: 4ac02fa15bf3809101e2eeb340b6078c - sha256: c79e62931e163b65a01786d5ed7156ecca2ab748b7c7835756d34e438035706a + md5: bd68a03d89eb296fe3b41ee9a6696341 + sha256: b37e42dda013641efd411b5f5cd85e49dbc874943b5a7b4cc2628e3c2cb1837e category: main optional: false - name: aws-c-sdkutils @@ -609,12 +609,12 @@ package: platform: linux-64 dependencies: __glibc: '>=2.17,<3.0.a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' libgcc: '>=14' - url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-sdkutils-0.2.7-h720e601_2.conda + url: https://repo.prefix.dev/conda-forge/linux-64/aws-c-sdkutils-0.2.7-haaf8e00_1.conda hash: - md5: 816f62fa82532118ebb2398382090945 - sha256: e419e179e04021fc680d98af23fac4b4c2369cea61171087e57a734e968dd9bd + md5: 2eb431d738f8e5ee90aff8ac5d255456 + sha256: e2653b11b1b222c9189cdf0362be5e2d347589d9ad5dfad25509860852e3ba85 category: main optional: false - name: aws-c-sdkutils @@ -622,14 +622,14 @@ package: manager: conda platform: win-64 dependencies: - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/aws-c-sdkutils-0.2.7-h1da161f_2.conda + url: https://repo.prefix.dev/conda-forge/win-64/aws-c-sdkutils-0.2.7-h4c63076_1.conda hash: - md5: 882d834f12e0abf4c7a0e9bb0c72e281 - sha256: d5da00123d9ceb082e61fd5bf82fff9cd5bec0b8e1ae91454f29155a59499bf5 + md5: 4914281d91c9385ae6a57be69489cc16 + sha256: fc4a86838f740f1639f12fe39eaa6550126dd7330fbd0b7c4b85246f40e4ea2c category: main optional: false - name: aws-checksums @@ -638,12 +638,12 @@ package: platform: linux-64 dependencies: __glibc: '>=2.17,<3.0.a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' libgcc: '>=14' - url: https://repo.prefix.dev/conda-forge/linux-64/aws-checksums-0.2.10-h720e601_4.conda + url: https://repo.prefix.dev/conda-forge/linux-64/aws-checksums-0.2.10-haaf8e00_3.conda hash: - md5: 24ee781effc5779206a80139323c9caf - sha256: 6cc8617854a43040291dea791fe111ba408e7a5bbd9ee9e5a99375da7a16846b + md5: 394a24d9d451020541ba28c27fc6ef16 + sha256: 90c4d073871f309cc35bc66da55eb169c9ac27790c51670963fd0a36a6e46f0d category: main optional: false - name: aws-checksums @@ -651,14 +651,14 @@ package: manager: conda platform: win-64 dependencies: - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/aws-checksums-0.2.10-h1da161f_4.conda + url: https://repo.prefix.dev/conda-forge/win-64/aws-checksums-0.2.10-h4c63076_3.conda hash: - md5: a3c81085892f2983979836f2937291ce - sha256: 2227fa77f395f1b4699533709e7ef140a8292fccb31376ec5498565c7ad33225 + md5: 3a97d5f5d53f730c6d03a3034f99f00b + sha256: e403379e7686613f198474f56dc7019bbe5cc080baa6dd55ceab6270a193e53e category: main optional: false - name: babel @@ -964,16 +964,16 @@ package: category: main optional: false - name: c-ares - version: 1.34.8 + version: 1.34.6 manager: conda platform: linux-64 dependencies: __glibc: '>=2.17,<3.0.a0' libgcc: '>=14' - url: https://repo.prefix.dev/conda-forge/linux-64/c-ares-1.34.8-hb03c661_0.conda + url: https://repo.prefix.dev/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda hash: - md5: 6130ad6705adc993b5d8482b7f66e01f - sha256: dee93a82d045f9aa8277829aa465224afa3c7a6ac18388de66099b2be7f705e7 + md5: 920bb03579f15389b9e512095ad995b7 + sha256: cc9accf72fa028d31c2a038460787751127317dcfa991f8d1f1babf216bb454e category: main optional: false - name: ca-certificates @@ -1049,27 +1049,27 @@ package: category: main optional: false - name: certifi - version: 2026.7.22 + version: 2026.6.17 manager: conda platform: linux-64 dependencies: python: '>=3.10' - url: https://repo.prefix.dev/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/certifi-2026.6.17-pyhd8ed1ab_0.conda hash: - md5: 37e13edbe3b48f1095a9d085ef9cd83b - sha256: fb167de4388e64e52aa3907ed099afab944c1fa6e5f74b281a312dae1bcf7f3b + md5: c13824fedced67005d3832c152fe9c2f + sha256: 6c13620e458ba43278379d0cdacc30c497336bddfda81681fd50d114a65c702f category: dev optional: true - name: certifi - version: 2026.7.22 + version: 2026.6.17 manager: conda platform: win-64 dependencies: python: '>=3.10' - url: https://repo.prefix.dev/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/certifi-2026.6.17-pyhd8ed1ab_0.conda hash: - md5: 37e13edbe3b48f1095a9d085ef9cd83b - sha256: fb167de4388e64e52aa3907ed099afab944c1fa6e5f74b281a312dae1bcf7f3b + md5: c13824fedced67005d3832c152fe9c2f + sha256: 6c13620e458ba43278379d0cdacc30c497336bddfda81681fd50d114a65c702f category: dev optional: true - name: cffi @@ -1264,7 +1264,7 @@ package: category: main optional: false - name: coverage - version: 7.15.2 + version: 7.15.0 manager: conda platform: linux-64 dependencies: @@ -1273,14 +1273,14 @@ package: python: '>=3.13,<3.14.0a0' python_abi: 3.13.* tomli: '' - url: https://repo.prefix.dev/conda-forge/linux-64/coverage-7.15.2-py313h3dea7bd_0.conda + url: https://repo.prefix.dev/conda-forge/linux-64/coverage-7.15.0-py313h3dea7bd_0.conda hash: - md5: 0ca456463b87657599aecdb9c664b3d8 - sha256: 83ae7eb4af7533369cd373320be5973de018b5dee01a43c42c06bf5a773ca306 + md5: 3ceff1a60d4b82c032a80711d5bcbcf5 + sha256: fe6396f100c27e752e6aaa60126d21223fad6d9814ef64fd8ae4a8aa482791e5 category: dev optional: true - name: coverage - version: 7.15.2 + version: 7.15.0 manager: conda platform: win-64 dependencies: @@ -1290,10 +1290,10 @@ package: ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/coverage-7.15.2-py313hd650c13_0.conda + url: https://repo.prefix.dev/conda-forge/win-64/coverage-7.15.0-py313hd650c13_0.conda hash: - md5: 02f704464af27eda68e63a4910bac78c - sha256: bed6036169a31b2b4171b165bc17e101cb9023bb66303b29306782b7f216619b + md5: 171467b5676fdf9c938a0a04f273f101 + sha256: 1a2dfc56507ddc0b1e0db1c6577adf929a0375cef12357130ac9d74d51bb4e56 category: dev optional: true - name: cpython @@ -2012,11 +2012,11 @@ package: platform: linux-64 dependencies: __glibc: '>=2.17,<3.0.a0' - aws-c-auth: '>=0.10.4,<0.10.5.0a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-auth: '>=0.10.3,<0.10.4.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' aws-c-http: '>=0.11.0,<0.11.1.0a0' - aws-c-io: '>=0.27.3,<0.27.4.0a0' - aws-c-s3: '>=0.12.8,<0.12.9.0a0' + aws-c-io: '>=0.26.3,<0.26.4.0a0' + aws-c-s3: '>=0.12.6,<0.12.7.0a0' aws-c-sdkutils: '>=0.2.7,<0.2.8.0a0' libaec: '>=1.1.5,<2.0a0' libcurl: '>=8.21.0,<9.0a0' @@ -2026,10 +2026,10 @@ package: libstdcxx: '>=14' libzlib: '>=1.3.2,<2.0a0' openssl: '>=3.5.7,<4.0a0' - url: https://repo.prefix.dev/conda-forge/linux-64/hdf5-2.1.0-nompi_h654f344_110.conda + url: https://repo.prefix.dev/conda-forge/linux-64/hdf5-2.1.0-nompi_hc8ae080_109.conda hash: - md5: 58484580a0f626dbe7d5145f014dbfd4 - sha256: 548411cb10baf1122c46cfef555936c385137d3637b75bd322e7574eda933842 + md5: 85e9d531a6f215a5002fd2174e2dcb35 + sha256: 6aa4790c0cdaf5bda8fdd7049c6645e6e456b368acccc8dc1c08336f9f75868f category: main optional: false - name: hdf5 @@ -2037,11 +2037,11 @@ package: manager: conda platform: win-64 dependencies: - aws-c-auth: '>=0.10.4,<0.10.5.0a0' - aws-c-common: '>=0.14.2,<0.14.3.0a0' + aws-c-auth: '>=0.10.3,<0.10.4.0a0' + aws-c-common: '>=0.14.1,<0.14.2.0a0' aws-c-http: '>=0.11.0,<0.11.1.0a0' - aws-c-io: '>=0.27.3,<0.27.4.0a0' - aws-c-s3: '>=0.12.8,<0.12.9.0a0' + aws-c-io: '>=0.26.3,<0.26.4.0a0' + aws-c-s3: '>=0.12.6,<0.12.7.0a0' aws-c-sdkutils: '>=0.2.7,<0.2.8.0a0' libaec: '>=1.1.5,<2.0a0' libcurl: '>=8.21.0,<9.0a0' @@ -2050,10 +2050,10 @@ package: ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/hdf5-2.1.0-nompi_h0d3e4b2_110.conda + url: https://repo.prefix.dev/conda-forge/win-64/hdf5-2.1.0-nompi_h0065017_109.conda hash: - md5: 146134e4db96613ecdc63cf44bec847d - sha256: f6c79581d03d2e2e0cbbb0391d21a149a1f9c311159a9f74c6be8a79e2f696d9 + md5: 174cdaf7ae4c6c6f1f0b085266aeee58 + sha256: c30f0c1d941b8e820c62652343f53ba63c09aef7f7587382cdc7637d1a4044a2 category: main optional: false - name: hpack @@ -2192,10 +2192,10 @@ package: ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/icu-78.3-h637d24d_1.conda + url: https://repo.prefix.dev/conda-forge/win-64/icu-78.3-h637d24d_0.conda hash: - md5: e3232ea6dafd9f12663f860a3194d6dc - sha256: 61f16ae57ee8956d5c1f69e703f7abaebb972d0def1edc5704d7198430398295 + md5: 0097b24800cb696915c3dbd1f5335d3f + sha256: 1bda728d70a619731b278c859eda364146cb5b4b8c739a64da8128353d81d1c4 category: main optional: false - name: idna @@ -2793,7 +2793,7 @@ package: category: dev optional: true - name: jupyter-builder - version: 1.1.1 + version: 1.1.0 manager: conda platform: linux-64 dependencies: @@ -2801,14 +2801,14 @@ package: python: '' tomli: '' traitlets: '' - url: https://repo.prefix.dev/conda-forge/noarch/jupyter-builder-1.1.1-pyhcf101f3_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/jupyter-builder-1.1.0-pyhcf101f3_0.conda hash: - md5: 3595d544a64a3225810a71e402aba766 - sha256: 876ead0b5381a732b4d3100b6494866d88d969ff428010d1e30f8350193dbf86 + md5: 67e33322de030b8b1852ee883002ec5b + sha256: 743f6ae17a6f6e37860958fa00db96bdec5c011bcef8b166bb8c84f3c957f7dc category: dev optional: true - name: jupyter-builder - version: 1.1.1 + version: 1.1.0 manager: conda platform: win-64 dependencies: @@ -2816,10 +2816,10 @@ package: python: '' tomli: '' traitlets: '' - url: https://repo.prefix.dev/conda-forge/noarch/jupyter-builder-1.1.1-pyhcf101f3_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/jupyter-builder-1.1.0-pyhcf101f3_0.conda hash: - md5: 3595d544a64a3225810a71e402aba766 - sha256: 876ead0b5381a732b4d3100b6494866d88d969ff428010d1e30f8350193dbf86 + md5: 67e33322de030b8b1852ee883002ec5b + sha256: 743f6ae17a6f6e37860958fa00db96bdec5c011bcef8b166bb8c84f3c957f7dc category: dev optional: true - name: jupyter-lsp @@ -3044,7 +3044,7 @@ package: category: dev optional: true - name: jupyterlab - version: 4.6.2 + version: 4.6.1 manager: conda platform: linux-64 dependencies: @@ -3063,15 +3063,14 @@ package: tomli: '>=1.2.2' tornado: '>=6.2.0' traitlets: '' - typing_extensions: '>=4.4.0' - url: https://repo.prefix.dev/conda-forge/noarch/jupyterlab-4.6.2-pyhd8ed1ab_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/jupyterlab-4.6.1-pyhd8ed1ab_0.conda hash: - md5: 74e2ef595d91aaffcf24815b4865bbc0 - sha256: a65dfe3aa15281377d3a589f0e86c463e6d8261b481bc892f7a40566ab1c675c + md5: 7ba07211c94d434f3223a4418f8b1ff8 + sha256: ad4a462da846fb8e9feebe39170553fc6a94252f4d4cca8efaf0dfeefa907099 category: dev optional: true - name: jupyterlab - version: 4.6.2 + version: 4.6.1 manager: conda platform: win-64 dependencies: @@ -3090,11 +3089,10 @@ package: tomli: '>=1.2.2' tornado: '>=6.2.0' traitlets: '' - typing_extensions: '>=4.4.0' - url: https://repo.prefix.dev/conda-forge/noarch/jupyterlab-4.6.2-pyhd8ed1ab_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/jupyterlab-4.6.1-pyhd8ed1ab_0.conda hash: - md5: 74e2ef595d91aaffcf24815b4865bbc0 - sha256: a65dfe3aa15281377d3a589f0e86c463e6d8261b481bc892f7a40566ab1c675c + md5: 7ba07211c94d434f3223a4418f8b1ff8 + sha256: ad4a462da846fb8e9feebe39170553fc6a94252f4d4cca8efaf0dfeefa907099 category: dev optional: true - name: jupyterlab_pygments @@ -3186,7 +3184,7 @@ package: category: dev optional: true - name: jupytext - version: 1.19.5 + version: 1.19.4 manager: conda platform: linux-64 dependencies: @@ -3197,14 +3195,14 @@ package: python: '>=3.10' pyyaml: '' tomli: '' - url: https://repo.prefix.dev/conda-forge/noarch/jupytext-1.19.5-pyh0398c0e_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/jupytext-1.19.4-pyh0398c0e_0.conda hash: - md5: 351c462ba9353930e745d620e6ceb09e - sha256: 1b0a1b68ee3d57dba85bdedfffd562b840b6914fd37afdf36f8f174c0d490b49 + md5: 9211ca1ee636d22fcec6df8fef0ce4e8 + sha256: 8aa4d40d6e2d4339067c36d30f29b4c7c16d14277fe4f581a3d605e5217314a8 category: dev optional: true - name: jupytext - version: 1.19.5 + version: 1.19.4 manager: conda platform: win-64 dependencies: @@ -3215,10 +3213,10 @@ package: python: '>=3.10' pyyaml: '' tomli: '' - url: https://repo.prefix.dev/conda-forge/noarch/jupytext-1.19.5-pyh0398c0e_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/jupytext-1.19.4-pyh0398c0e_0.conda hash: - md5: 351c462ba9353930e745d620e6ceb09e - sha256: 1b0a1b68ee3d57dba85bdedfffd562b840b6914fd37afdf36f8f174c0d490b49 + md5: 9211ca1ee636d22fcec6df8fef0ce4e8 + sha256: 8aa4d40d6e2d4339067c36d30f29b4c7c16d14277fe4f581a3d605e5217314a8 category: dev optional: true - name: keyutils @@ -3354,16 +3352,16 @@ package: category: main optional: false - name: ld_impl_linux-64 - version: 2.46.1 + version: 2.45.1 manager: conda platform: linux-64 dependencies: __glibc: '>=2.17,<3.0.a0' zstd: '>=1.5.7,<1.6.0a0' - url: https://repo.prefix.dev/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_102.conda + url: https://repo.prefix.dev/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda hash: - md5: 449500f2c089da11c40f5c21312e3e07 - sha256: 27d83f1188cd19bcb7754a078b3fa7f4cfb8527f8eb2fde54dd01fc529d1adec + md5: 18335a698559cdbcd86150a48bf54ba6 + sha256: 3d584956604909ff5df353767f3a2a2f60e07d070b328d109f30ac40cd62df6c category: main optional: false - name: lerc @@ -4757,29 +4755,29 @@ package: category: main optional: false - name: mistune - version: 3.3.4 + version: 3.3.3 manager: conda platform: linux-64 dependencies: python: '' typing_extensions: '' - url: https://repo.prefix.dev/conda-forge/noarch/mistune-3.3.4-pyhcf101f3_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/mistune-3.3.3-pyhcf101f3_0.conda hash: - md5: 1c0ebec41ef9214160da1ee6a0880fce - sha256: 306af633cb4ac85d04024d7c243ac2031c488bf5b0912207e7304c27a5d65a85 + md5: 6bda0cbb7b7608101bc2c49ee6f38de7 + sha256: 2e16a4297761697ed97f4c9974f5c350c7dbca8877a582c7793f4295db6c00a2 category: dev optional: true - name: mistune - version: 3.3.4 + version: 3.3.3 manager: conda platform: win-64 dependencies: python: '' typing_extensions: '' - url: https://repo.prefix.dev/conda-forge/noarch/mistune-3.3.4-pyhcf101f3_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/mistune-3.3.3-pyhcf101f3_0.conda hash: - md5: 1c0ebec41ef9214160da1ee6a0880fce - sha256: 306af633cb4ac85d04024d7c243ac2031c488bf5b0912207e7304c27a5d65a85 + md5: 6bda0cbb7b7608101bc2c49ee6f38de7 + sha256: 2e16a4297761697ed97f4c9974f5c350c7dbca8877a582c7793f4295db6c00a2 category: dev optional: true - name: mkl @@ -5160,39 +5158,39 @@ package: category: dev optional: true - name: notebook - version: 7.6.1 + version: 7.6.0 manager: conda platform: linux-64 dependencies: jupyter-builder: '>=1.0.2,<2' jupyter_server: '>=2.19.0,<3' - jupyterlab: '>=4.6.2,<4.7' + jupyterlab: '>=4.6.0,<4.7' jupyterlab_server: '>=2.28.0,<3' notebook-shim: '>=0.2,<0.3' python: '' tornado: '>=6.2.0' - url: https://repo.prefix.dev/conda-forge/noarch/notebook-7.6.1-pyhcf101f3_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/notebook-7.6.0-pyhcf101f3_0.conda hash: - md5: 5ae09febf4b5923fe454019c1b49ba78 - sha256: 5e4436013e83e7fb11d6a5c85a458a6e18364df6d0f50ee22dda534d1d5e6431 + md5: d9eb17006787e506bcb70f16daf1b1c1 + sha256: 5b48c26a966caae81169cc8056321a4c139103880eb01e1f4f02ad6ff9390421 category: dev optional: true - name: notebook - version: 7.6.1 + version: 7.6.0 manager: conda platform: win-64 dependencies: jupyter-builder: '>=1.0.2,<2' jupyter_server: '>=2.19.0,<3' - jupyterlab: '>=4.6.2,<4.7' + jupyterlab: '>=4.6.0,<4.7' jupyterlab_server: '>=2.28.0,<3' notebook-shim: '>=0.2,<0.3' python: '' tornado: '>=6.2.0' - url: https://repo.prefix.dev/conda-forge/noarch/notebook-7.6.1-pyhcf101f3_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/notebook-7.6.0-pyhcf101f3_0.conda hash: - md5: 5ae09febf4b5923fe454019c1b49ba78 - sha256: 5e4436013e83e7fb11d6a5c85a458a6e18364df6d0f50ee22dda534d1d5e6431 + md5: d9eb17006787e506bcb70f16daf1b1c1 + sha256: 5b48c26a966caae81169cc8056321a4c139103880eb01e1f4f02ad6ff9390421 category: dev optional: true - name: notebook-shim @@ -5693,27 +5691,27 @@ package: category: main optional: false - name: platformdirs - version: 4.11.0 + version: 4.10.0 manager: conda platform: linux-64 dependencies: python: '' - url: https://repo.prefix.dev/conda-forge/noarch/platformdirs-4.11.0-pyhcf101f3_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/platformdirs-4.10.0-pyhcf101f3_0.conda hash: - md5: 1fadaa6dd1d03d062075f84157ac2cc7 - sha256: a4b8c7d3b3703d10f93187986d59aec09b22033978945845899beb7f849a7be6 + md5: 2c5ef45db85d34799771629bd5860fd7 + sha256: 9e5e1fd3506ccfc4d444fc4d2d39b0ed097d5d0e3bd3d4bdf6bcc81aaf66860d category: dev optional: true - name: platformdirs - version: 4.11.0 + version: 4.10.0 manager: conda platform: win-64 dependencies: python: '' - url: https://repo.prefix.dev/conda-forge/noarch/platformdirs-4.11.0-pyhcf101f3_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/platformdirs-4.10.0-pyhcf101f3_0.conda hash: - md5: 1fadaa6dd1d03d062075f84157ac2cc7 - sha256: a4b8c7d3b3703d10f93187986d59aec09b22033978945845899beb7f849a7be6 + md5: 2c5ef45db85d34799771629bd5860fd7 + sha256: 9e5e1fd3506ccfc4d444fc4d2d39b0ed097d5d0e3bd3d4bdf6bcc81aaf66860d category: dev optional: true - name: pluggy @@ -6245,6 +6243,7 @@ package: libzlib: '>=1.3.2,<2.0a0' ncurses: '>=6.6,<7.0a0' openssl: '>=3.5.7,<4.0a0' + pip: '' python_abi: 3.13.* readline: '>=8.3,<9.0a0' tk: '>=8.6.13,<8.7.0a0' @@ -6268,6 +6267,7 @@ package: libsqlite: '>=3.53.2,<4.0a0' libzlib: '>=1.3.2,<2.0a0' openssl: '>=3.5.7,<4.0a0' + pip: '' python_abi: 3.13.* tk: '>=8.6.13,<8.7.0a0' tzdata: '' @@ -7103,27 +7103,27 @@ package: category: main optional: false - name: soupsieve - version: 2.9.1 + version: 2.8.4 manager: conda platform: linux-64 dependencies: python: '>=3.10' - url: https://repo.prefix.dev/conda-forge/noarch/soupsieve-2.9.1-pyhd8ed1ab_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/soupsieve-2.8.4-pyhd8ed1ab_0.conda hash: - md5: 0fc47d7f5a6dbafd186a8f92cad5de3f - sha256: d10bfe45ffd6fa37baef69d852f9fa882be4e6fe4cdd7286f3543e79b803cadb + md5: 9e21f087f087f805debe877d88e00a14 + sha256: 2afa5fe9331c09b4c4689ddf6ace8fc16c837eae547c57dab325b844072fdd77 category: dev optional: true - name: soupsieve - version: 2.9.1 + version: 2.8.4 manager: conda platform: win-64 dependencies: python: '>=3.10' - url: https://repo.prefix.dev/conda-forge/noarch/soupsieve-2.9.1-pyhd8ed1ab_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/soupsieve-2.8.4-pyhd8ed1ab_0.conda hash: - md5: 0fc47d7f5a6dbafd186a8f92cad5de3f - sha256: d10bfe45ffd6fa37baef69d852f9fa882be4e6fe4cdd7286f3543e79b803cadb + md5: 9e21f087f087f805debe877d88e00a14 + sha256: 2afa5fe9331c09b4c4689ddf6ace8fc16c837eae547c57dab325b844072fdd77 category: dev optional: true - name: sphinx @@ -7509,11 +7509,11 @@ package: dependencies: __glibc: '>=2.17,<3.0.a0' libgcc: '>=14' - libzlib: '>=1.3.2,<2.0a0' - url: https://repo.prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_hd70dff1_3.conda + libzlib: '>=1.3.1,<2.0a0' + url: https://repo.prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda hash: - md5: 48a1049e710857572fc2a832aa394d9f - sha256: 43624eab22f5f29df7d6ffe914cf442f28fd559b55b290906255492826e636e8 + md5: cffd3bdd58090148f4cfcd831f4b26ab + sha256: cafeec44494f842ffeca27e9c8b0c27ed714f93ac77ddadc6aaf726b5554ebac category: main optional: false - name: tk @@ -7524,10 +7524,10 @@ package: ucrt: '>=10.0.20348.0' vc: '>=14.3,<15' vc14_runtime: '>=14.44.35208' - url: https://repo.prefix.dev/conda-forge/win-64/tk-8.6.13-h967ab96_3.conda + url: https://repo.prefix.dev/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda hash: - md5: aaf79e2af50a151fb5b5a3e3f38b7a69 - sha256: 13fa29257d43f8e630a1e591ed77fae9bbbb236b011432f01e2034cf36e6bf03 + md5: 0481bfd9814bf525bd4b3ee4b51494c4 + sha256: 0e79810fae28f3b69fe7391b0d43f5474d6bd91d451d5f2bde02f55ae481d5e3 category: main optional: false - name: tomli @@ -7555,27 +7555,27 @@ package: category: dev optional: true - name: tomlkit - version: 0.15.1 + version: 0.15.0 manager: conda platform: linux-64 dependencies: - python: '' - url: https://repo.prefix.dev/conda-forge/noarch/tomlkit-0.15.1-pyhcf101f3_0.conda + python: '>=3.10' + url: https://repo.prefix.dev/conda-forge/noarch/tomlkit-0.15.0-pyha770c72_0.conda hash: - md5: e4942e2de7436ff28be7f5645cf3c8d6 - sha256: 5cf833b4d199688b7652fb322ecc134de9555e9444d9249750de9a153421ad0a + md5: 42ef10a8f7f5d55a2e267c0d5daa6387 + sha256: 1cd52f9ccb4854c4d731438afe0e833b6b71edaf5ede661152aa98efb3a7cc70 category: dev optional: true - name: tomlkit - version: 0.15.1 + version: 0.15.0 manager: conda platform: win-64 dependencies: - python: '' - url: https://repo.prefix.dev/conda-forge/noarch/tomlkit-0.15.1-pyhcf101f3_0.conda + python: '>=3.10' + url: https://repo.prefix.dev/conda-forge/noarch/tomlkit-0.15.0-pyha770c72_0.conda hash: - md5: e4942e2de7436ff28be7f5645cf3c8d6 - sha256: 5cf833b4d199688b7652fb322ecc134de9555e9444d9249750de9a153421ad0a + md5: 42ef10a8f7f5d55a2e267c0d5daa6387 + sha256: 1cd52f9ccb4854c4d731438afe0e833b6b71edaf5ede661152aa98efb3a7cc70 category: dev optional: true - name: toolz @@ -7634,30 +7634,30 @@ package: category: main optional: false - name: tqdm - version: 4.69.0 + version: 4.68.4 manager: conda platform: linux-64 dependencies: __unix: '' python: '' - url: https://repo.prefix.dev/conda-forge/noarch/tqdm-4.69.0-pyh8f84b5b_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/tqdm-4.68.4-pyh8f84b5b_0.conda hash: - md5: c0f814b31ce7e2d0d5b5756156ebe144 - sha256: d1ec99b77153728a956a9be0d6767969bf0126869ad76f71dc166c2601ad82ad + md5: 54772f97dc361b1659ef0b34d71d39b4 + sha256: 1964320e89c00a531705449187f4b8ed85f935c73f13ba55de3a6b35b05443fa category: main optional: false - name: tqdm - version: 4.69.0 + version: 4.68.4 manager: conda platform: win-64 dependencies: __win: '' colorama: '' python: '' - url: https://repo.prefix.dev/conda-forge/noarch/tqdm-4.69.0-pyha7b4d00_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/tqdm-4.68.4-pyha7b4d00_0.conda hash: - md5: 4eb25e44c870dfbdd9bb34e29f8945f6 - sha256: fad3a696f659aab91c62f2c19b3172c7acf558acd9f180fc49955bffe22398cf + md5: a4708e48840dbcca9cb9c4344226080c + sha256: fc8b4e3ec4e8d8f0b0bd399514e70141e0b1726f2ec58b6439158f6a20c02a23 category: main optional: false - name: traitlets @@ -7809,25 +7809,25 @@ package: category: dev optional: true - name: tzdata - version: 2026c + version: 2025c manager: conda platform: linux-64 dependencies: {} - url: https://repo.prefix.dev/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda hash: - md5: fcb489df604d100968b737f2cb6076c6 - sha256: b928c30ddcb0e3f544c6eade8352737e6e610e263276b90232db6a578ef899d8 + md5: ad659d0a2b3e47e38d829aa8cad2d610 + sha256: 1d30098909076af33a35017eed6f2953af1c769e273a0626a04722ac4acaba3c category: main optional: false - name: tzdata - version: 2026c + version: 2025c manager: conda platform: win-64 dependencies: {} - url: https://repo.prefix.dev/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + url: https://repo.prefix.dev/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda hash: - md5: fcb489df604d100968b737f2cb6076c6 - sha256: b928c30ddcb0e3f544c6eade8352737e6e610e263276b90232db6a578ef899d8 + md5: ad659d0a2b3e47e38d829aa8cad2d610 + sha256: 1d30098909076af33a35017eed6f2953af1c769e273a0626a04722ac4acaba3c category: main optional: false - name: ucrt @@ -8413,105 +8413,121 @@ package: category: main optional: false - name: geoapps-utils - version: 0.7.0 + version: 0.7.1.dev36+2deafcc manager: pip platform: linux-64 dependencies: - geoh5py: '>=0.13.0b1,<0.14.dev0' + geoh5py: 0.13.1.dev114+3722011a matplotlib: '>=3.10.0,<3.11.0' numpy: '>=2.4.2,<2.5.0' pydantic: '>=2.12.0,<2.13.0' scipy: '>=1.17.0,<1.18.0' - url: https://files.pythonhosted.org/packages/97/98/2f55e08a24ec14e493d8f83d425e353512f0800bd6d36cd62bea0169fff0/geoapps_utils-0.7.0-py3-none-any.whl + url: git+https://github.com/MiraGeoscience/geoapps-utils.git@2deafccc38a40039dc6b3f376bf0e3c9d0371498 hash: - sha256: 97796a9a1dbaed392eb2be97a1e66f2ae894b64460bdb940f42a945e31e2c417 + sha256: 2deafccc38a40039dc6b3f376bf0e3c9d0371498 + source: + type: url + url: git+https://github.com/MiraGeoscience/geoapps-utils.git@2deafccc38a40039dc6b3f376bf0e3c9d0371498 category: main optional: false - name: geoapps-utils - version: 0.7.0 + version: 0.7.1.dev36+2deafcc manager: pip platform: win-64 dependencies: - geoh5py: '>=0.13.0b1,<0.14.dev0' + geoh5py: 0.13.1.dev114+3722011a matplotlib: '>=3.10.0,<3.11.0' numpy: '>=2.4.2,<2.5.0' pydantic: '>=2.12.0,<2.13.0' scipy: '>=1.17.0,<1.18.0' - url: https://files.pythonhosted.org/packages/97/98/2f55e08a24ec14e493d8f83d425e353512f0800bd6d36cd62bea0169fff0/geoapps_utils-0.7.0-py3-none-any.whl + url: git+https://github.com/MiraGeoscience/geoapps-utils.git@2deafccc38a40039dc6b3f376bf0e3c9d0371498 hash: - sha256: 97796a9a1dbaed392eb2be97a1e66f2ae894b64460bdb940f42a945e31e2c417 + sha256: 2deafccc38a40039dc6b3f376bf0e3c9d0371498 + source: + type: url + url: git+https://github.com/MiraGeoscience/geoapps-utils.git@2deafccc38a40039dc6b3f376bf0e3c9d0371498 category: main optional: false - name: geoh5py - version: 0.13.1rc2 + version: 0.13.1.dev114+3722011a manager: pip platform: linux-64 dependencies: h5py: '>=3.15.0,<3.16.0' numpy: '>=2.4.0,<2.5.0' - pillow: '>=12.2.0,<13.0.0' + pillow: '>=12.2.0,<12.3.0' psutil: '>=7.2.2,<7.3.0' pydantic: '>=2.12.0,<2.13.0' - url: https://files.pythonhosted.org/packages/dc/74/001da71889b7f9bd1f6e68bde1bcac0fd84da998f2deced929bf01ff62ef/geoh5py-0.13.1rc2-py3-none-any.whl + url: git+https://github.com/MiraGeoscience/geoh5py.git@3722011aa9fd8512f1bd6a3e58e2220a88ca8ce2 hash: - sha256: 3a01ce48994bad4bd2ee0f7f97eb8bdf6bba490b11d9cbbaac12f1a0f300e389 + sha256: 3722011aa9fd8512f1bd6a3e58e2220a88ca8ce2 + source: + type: url + url: git+https://github.com/MiraGeoscience/geoh5py.git@3722011aa9fd8512f1bd6a3e58e2220a88ca8ce2 category: main optional: false - name: geoh5py - version: 0.13.1rc2 + version: 0.13.1.dev114+3722011a manager: pip platform: win-64 dependencies: h5py: '>=3.15.0,<3.16.0' numpy: '>=2.4.0,<2.5.0' - pillow: '>=12.2.0,<13.0.0' + pillow: '>=12.2.0,<12.3.0' psutil: '>=7.2.2,<7.3.0' pydantic: '>=2.12.0,<2.13.0' - url: https://files.pythonhosted.org/packages/dc/74/001da71889b7f9bd1f6e68bde1bcac0fd84da998f2deced929bf01ff62ef/geoh5py-0.13.1rc2-py3-none-any.whl + url: git+https://github.com/MiraGeoscience/geoh5py.git@3722011aa9fd8512f1bd6a3e58e2220a88ca8ce2 hash: - sha256: 3a01ce48994bad4bd2ee0f7f97eb8bdf6bba490b11d9cbbaac12f1a0f300e389 + sha256: 3722011aa9fd8512f1bd6a3e58e2220a88ca8ce2 + source: + type: url + url: git+https://github.com/MiraGeoscience/geoh5py.git@3722011aa9fd8512f1bd6a3e58e2220a88ca8ce2 category: main optional: false - name: grid-apps - version: 0.2.0 + version: 0.2.0b3.dev38+7bc33d1 manager: pip platform: linux-64 dependencies: discretize: '>=0.12.0,<0.13.0' - geoapps-utils: '>=0.7.0b1,<0.8.dev0' - geoh5py: '>=0.13.0b1,<0.14.dev0' + geoapps-utils: 0.7.1.dev36+2deafcc + geoh5py: 0.13.1.dev114+3722011a numpy: '>=2.4.2,<2.5.0' pydantic: '>=2.12.0,<2.13.0' scipy: '>=1.17.0,<1.18.0' - url: https://files.pythonhosted.org/packages/79/05/70c54f61dcab22dea9f0d16f2218c67ce8e4174ce70354db129244379a7e/grid_apps-0.2.0-py3-none-any.whl + url: git+https://github.com/MiraGeoscience/grid-apps.git@7bc33d170f03dd6da15ee531b49cdb5835b36ea4 hash: - sha256: 77dd1881c3467687f7b1698eefc285368f0f84a1b154651e97006b36a668fa54 + sha256: 7bc33d170f03dd6da15ee531b49cdb5835b36ea4 + source: + type: url + url: git+https://github.com/MiraGeoscience/grid-apps.git@7bc33d170f03dd6da15ee531b49cdb5835b36ea4 category: main optional: false - name: grid-apps - version: 0.2.0 + version: 0.2.0b3.dev38+7bc33d1 manager: pip platform: win-64 dependencies: discretize: '>=0.12.0,<0.13.0' - geoapps-utils: '>=0.7.0b1,<0.8.dev0' - geoh5py: '>=0.13.0b1,<0.14.dev0' + geoapps-utils: 0.7.1.dev36+2deafcc + geoh5py: 0.13.1.dev114+3722011a numpy: '>=2.4.2,<2.5.0' pydantic: '>=2.12.0,<2.13.0' scipy: '>=1.17.0,<1.18.0' - url: https://files.pythonhosted.org/packages/79/05/70c54f61dcab22dea9f0d16f2218c67ce8e4174ce70354db129244379a7e/grid_apps-0.2.0-py3-none-any.whl + url: git+https://github.com/MiraGeoscience/grid-apps.git@7bc33d170f03dd6da15ee531b49cdb5835b36ea4 hash: - sha256: 77dd1881c3467687f7b1698eefc285368f0f84a1b154651e97006b36a668fa54 + sha256: 7bc33d170f03dd6da15ee531b49cdb5835b36ea4 + source: + type: url + url: git+https://github.com/MiraGeoscience/grid-apps.git@7bc33d170f03dd6da15ee531b49cdb5835b36ea4 category: main optional: false - name: mira-simpeg - version: 0.25.0.1 + version: 0.25.0.2.dev11+g87230755b manager: pip platform: linux-64 dependencies: - dask: '*' discretize: '>=0.11' - fsspec: '>=0.3.3' geoana: '>=0.7.0' geoh5py: '>=0.13.0b1' libdlf: '*' @@ -8519,20 +8535,20 @@ package: numpy: '>=1.22' pymatsolver: '>=0.3' scipy: '>=1.8' - zarr: '*' - url: https://files.pythonhosted.org/packages/c4/f7/f7a5357a165f52e2de83878d20cbf236cf6f5348470008140afd7fdde6ba/mira_simpeg-0.25.0.1-py3-none-any.whl + url: git+https://github.com/MiraGeoscience/simpeg.git@87230755b40f3961fd9b2383df191c140a29c601 hash: - sha256: 8bceaaf017df95ea925e20feaa2e6a5dc0f8fede434738d6493c775cb5341b38 + sha256: 87230755b40f3961fd9b2383df191c140a29c601 + source: + type: url + url: git+https://github.com/MiraGeoscience/simpeg.git@87230755b40f3961fd9b2383df191c140a29c601 category: main optional: false - name: mira-simpeg - version: 0.25.0.1 + version: 0.25.0.2.dev11+g87230755b manager: pip platform: win-64 dependencies: - dask: '*' discretize: '>=0.11' - fsspec: '>=0.3.3' geoana: '>=0.7.0' geoh5py: '>=0.13.0b1' libdlf: '*' @@ -8540,9 +8556,11 @@ package: numpy: '>=1.22' pymatsolver: '>=0.3' scipy: '>=1.8' - zarr: '*' - url: https://files.pythonhosted.org/packages/c4/f7/f7a5357a165f52e2de83878d20cbf236cf6f5348470008140afd7fdde6ba/mira_simpeg-0.25.0.1-py3-none-any.whl + url: git+https://github.com/MiraGeoscience/simpeg.git@87230755b40f3961fd9b2383df191c140a29c601 hash: - sha256: 8bceaaf017df95ea925e20feaa2e6a5dc0f8fede434738d6493c775cb5341b38 + sha256: 87230755b40f3961fd9b2383df191c140a29c601 + source: + type: url + url: git+https://github.com/MiraGeoscience/simpeg.git@87230755b40f3961fd9b2383df191c140a29c601 category: main optional: false diff --git a/pyproject.toml b/pyproject.toml index 076717c7e..2ac06cadc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,17 +93,17 @@ trimesh = "^4.1.3" ## Pip dependencies from Git repositories #---------------------------------------- -geoh5py = {version = ">=0.13.1rc2, 0.13.*", source = "pypi", allow-prereleases = true} -# geoh5py = {git = "https://github.com/MiraGeoscience/geoh5py.git", rev = "release/GA_4.8.1"} +# geoh5py = {version = ">=0.14.0a, 0.14.*", source = "pypi", allow-prereleases = true} +geoh5py = {git = "https://github.com/MiraGeoscience/geoh5py.git", rev = "develop"} -grid-apps = {version = ">=0.2.0, 0.2.*", source = "pypi", allow-prereleases = true} -# grid-apps = {git = "https://github.com/MiraGeoscience/grid-apps.git", rev = "release/GA_4.8"} +# grid-apps = {version = ">=0.3.0a, 0.3.*", source = "pypi", allow-prereleases = true} +grid-apps = {git = "https://github.com/MiraGeoscience/grid-apps.git", rev = "develop"} -geoapps-utils = {version = ">=0.7.0, 0.7.*", source = "pypi", allow-prereleases = true} -# geoapps-utils = {git = "https://github.com/MiraGeoscience/geoapps-utils.git", rev = "release/GA_4.8"} +# geoapps-utils = {version = ">=0.8.0a, 0.8.*", source = "pypi", allow-prereleases = true} +geoapps-utils = {git = "https://github.com/MiraGeoscience/geoapps-utils.git", rev = "develop"} -mira-simpeg = {version = ">=0.25.0.1, 0.25.0.*", source="pypi", allow-prereleases = true, extras = ["dask"]} -# mira-simpeg = {git = "https://github.com/MiraGeoscience/simpeg.git", rev = "release/GA_4.8", extras = ["dask"]} +# mira-simpeg = {version = ">=0.25.0.2a, 0.25.0.*", source="pypi", allow-prereleases = true, extras = ["dask"]} +mira-simpeg = {git = "https://github.com/MiraGeoscience/simpeg.git", rev = "develop", extras = ["dask"]} ## about pip dependencies # to be specified to work with conda-lock @@ -142,7 +142,7 @@ bokeh = "3.6.*" # optional, for dask reporting fsspec = ">=2022.0" # from simpeg[dask] geoana = "0.8.*" # from simpeg h5py = ">=3.15.1, 3.*" # from geoh5py -pillow = ">=12.2.0, 12.*" # from geoh5py +pillow = ">=12.1.0, 12.*" # from geoh5py psutil = ">=7.2.2, 7.2.*" # from geoh5py pymatsolver = "0.3.*" # from simpeg zarr = ">=2.14.2, 2.14.*" # from simpeg[dask] diff --git a/simpeg_drivers-assets/uijson/plate_simulation.ui.json b/simpeg_drivers-assets/uijson/plate_simulation.ui.json index 34e3a9d75..56e87fcbc 100644 --- a/simpeg_drivers-assets/uijson/plate_simulation.ui.json +++ b/simpeg_drivers-assets/uijson/plate_simulation.ui.json @@ -18,6 +18,12 @@ "enabled": true, "tooltip": "Forward modelling SimPEG group with at least the topography and survey set" }, + "use_leroi": { + "main": true, + "label": "Use LeroiAir", + "value": false, + "tooltip": "If checked, plate simulation will use LeroiAir to simulate the plate response." + }, "name": { "main": true, "label": "Label", diff --git a/simpeg_drivers/components/factories/directives_factory.py b/simpeg_drivers/components/factories/directives_factory.py index 4b89aec91..c7b9c5631 100644 --- a/simpeg_drivers/components/factories/directives_factory.py +++ b/simpeg_drivers/components/factories/directives_factory.py @@ -123,10 +123,10 @@ def inversion_directives(self): for directive in [ "vector_inversion_directive", "update_irls_directive", + "scale_misfits", "update_sensitivity_weights_directive", "beta_estimate_by_eigenvalues_directive", "update_preconditioner_directive", - "scale_misfits", ]: if getattr(self, directive) is not None: directives_list.append(getattr(self, directive)) @@ -293,6 +293,7 @@ def scale_misfits(self): / f"{self.params.geoh5.h5file.stem}.chi", nested_tiles, target_chi=self.params.cooling_schedule.chi_factor, + cooling_factor=self.params.cooling_schedule.cooling_factor, ) return self._scale_misfits @@ -424,13 +425,7 @@ def assemble_keyword_arguments( active_cells_map, inversion_object.permutation.T, ] - data_type = DataType.find_or_create_type( - self.params.geoh5, - "FLOAT", - ) - angles = np.linspace(0, 1, 90) - colormap = np.c_[angles * 360, colormaps["twilight"](angles) * 255] - data_type.color_map = ColorMap(name="twilight.TBL", values=colormap) + data_type = self.find_or_create_angle_data_type() kwargs["data_type"] = { "": { 1: data_type, @@ -468,6 +463,27 @@ def assemble_keyword_arguments( return kwargs + def find_or_create_angle_data_type(self) -> DataType: + """ + Look for existing data type for angles in the geoh5 workspace. + If not found, create a new data type with a colormap for angles. + """ + for entity_type in self.params.geoh5.types: + if ( + isinstance(entity_type, DataType) + and entity_type.name == "Degree angles" + ): + return entity_type + + data_type = DataType.find_or_create_type( + self.params.geoh5, "FLOAT", name="Degree angles" + ) + angles = np.linspace(0, 1, 90) + colormap = np.c_[angles * 360, colormaps["twilight"](angles) * 255] + data_type.color_map = ColorMap(name="twilight.TBL", values=colormap) + + return data_type + class SaveSensitivitiesGeoh5Factory(SaveGeoh5Factory): """ diff --git a/simpeg_drivers/components/factories/receiver_factory.py b/simpeg_drivers/components/factories/receiver_factory.py index 7e48eb9ca..3befa904a 100644 --- a/simpeg_drivers/components/factories/receiver_factory.py +++ b/simpeg_drivers/components/factories/receiver_factory.py @@ -199,7 +199,6 @@ def assemble_keyword_arguments( ): orientations = self.orientations[kwargs["orientation"]][local_indices, :] - # TODO: GEOPY-2880: Generalize simpeg to allow 2D array of orientations if orientations.ndim == 2: orientations = np.mean(orientations, axis=0) diff --git a/simpeg_drivers/components/factories/simpeg_factory.py b/simpeg_drivers/components/factories/simpeg_factory.py index 9f6d08960..853aa76cc 100644 --- a/simpeg_drivers/components/factories/simpeg_factory.py +++ b/simpeg_drivers/components/factories/simpeg_factory.py @@ -20,8 +20,6 @@ from simpeg_drivers.options import BaseOptions -# TODO Redesign simpeg factory to avoid pylint arguments-differ complaint - class SimPEGFactory(ABC): """ diff --git a/simpeg_drivers/components/meshes.py b/simpeg_drivers/components/meshes.py index bf2934a9d..5af483927 100644 --- a/simpeg_drivers/components/meshes.py +++ b/simpeg_drivers/components/meshes.py @@ -29,7 +29,6 @@ logger = getLogger(__name__) -# TODO: Import this from newer octree-creation-app release def tree_levels(mesh: Octree) -> np.ndarray | None: """ Convert Octree n cell indices to Treemesh level indices. diff --git a/simpeg_drivers/components/models.py b/simpeg_drivers/components/models.py index 330b6b544..db0eba92a 100644 --- a/simpeg_drivers/components/models.py +++ b/simpeg_drivers/components/models.py @@ -152,14 +152,17 @@ def active_cells(self, active_cells: np.ndarray | NumericData | None): permutation = self.driver.inversion_mesh.permutation self.edit_ndv_model(permutation.T @ active_cells) self.remove_air(active_cells) - self.driver.inversion_mesh.entity.add_data( - { - "active_cells": { - "values": permutation.T @ active_cells, - "primitive_type": "boolean", + + if not self.driver.inversion_mesh.entity.get_data("active_cells"): + self.driver.inversion_mesh.entity.add_data( + { + "active_cells": { + "values": permutation.T @ active_cells, + "primitive_type": "boolean", + } } - } - ) + ) + self._active_cells = active_cells @property diff --git a/simpeg_drivers/components/topography.py b/simpeg_drivers/components/topography.py index a54c19abc..0d0bf9107 100644 --- a/simpeg_drivers/components/topography.py +++ b/simpeg_drivers/components/topography.py @@ -121,7 +121,7 @@ def active_cells(self, mesh: InversionMesh, data: InversionData) -> np.ndarray: active_cells = active_from_xyz( mesh.entity, vertices, - grid_reference="center", + grid_reference="bottom" if forced_to_surface else "center", triangulation=cells, ) diff --git a/simpeg_drivers/driver.py b/simpeg_drivers/driver.py index 752b5daf4..d42edbd9e 100644 --- a/simpeg_drivers/driver.py +++ b/simpeg_drivers/driver.py @@ -8,11 +8,12 @@ # ' # ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' - +# pylint: disable=too-many-lines # flake8: noqa from __future__ import annotations +from io import BytesIO from abc import abstractmethod, ABC from typing import Self from copy import deepcopy @@ -23,6 +24,7 @@ from time import time import numpy as np +from pandas import read_csv from dask.distributed import get_client, Client from geoapps_utils.base import Driver, Options @@ -30,8 +32,9 @@ from geoapps_utils.utils.importing import GeoAppsError from geoh5py import Workspace +from geoh5py.data import FilenameData from geoh5py.groups import SimPEGGroup -from geoh5py.objects import FEMSurvey +from geoh5py.objects import DrapeModel, FEMSurvey, Octree from geoh5py.shared.utils import fetch_active_workspace from geoh5py.ui_json import BaseUIJson @@ -74,9 +77,10 @@ from simpeg_drivers.utils.nested import tile_locations from simpeg_drivers.utils.regularization import cell_neighbors, set_rotated_operators from simpeg_drivers.utils.utils import ( - validate_out_group, - start_dask_run, + argument_parser, driver_class_from_dict, + start_dask_run, + validate_out_group, ) mlogger = logging.getLogger("distributed") @@ -462,19 +466,35 @@ def start_message(self): Starting message displayed by the logger. """ - def run(self): - """Run inversion from params""" + def run(self, start_iteration: int = -1): + """ + Run inversion from params + + :param start_iteration: Whether to warm-start the inversion. + """ if self.logger: sys.stdout = self.logger - self.logger.start() with fetch_active_workspace(self.workspace, mode="r+"): - if Path(self.params.input_file.path_name).is_file(): - self.out_group.add_file(self.params.input_file.path_name) - try: - self.start_message() + if any( + [ + child + for child in self.out_group.children + if child.name.endswith(".out") + ] + ): + self.warm_start(start_iteration) + else: + if Path(self.params.input_file.path_name).is_file(): + self.out_group.add_file(self.params.input_file.path_name) + + if self.logger: + self.logger.start() + + self.start_message() + self.simpeg_run() except np.core._exceptions._ArrayMemoryError as error: # pylint: disable=protected-access @@ -493,13 +513,19 @@ def run(self): self.directives.save_iteration_log_files.write(1) @classmethod - def start(cls, filepath: str | Path | BaseUIJson, mode="r+", **kwargs) -> Self: + def start( + cls, + filepath: str | Path | BaseUIJson, + mode="r+", + start_iteration: int = -1, + **kwargs, + ) -> Self: """ Run application specified by 'filepath' ui.json file. - TODO: To be replaced by the base Driver class implementation on geoapps_utils :param filepath: Path to valid ui.json file for the application driver. :param mode: Mode to open the geoh5 file with. + :param start_iteration: Iteration to warm-start the inversion if possible. Defaults to last iteration (-1). :param kwargs: Additional keyword arguments for Options class. :return: Self object. @@ -523,7 +549,7 @@ def start(cls, filepath: str | Path | BaseUIJson, mode="r+", **kwargs) -> Self: kwargs.update(data) params = cls._params_class.build(**kwargs) driver = cls(params) - driver.run() + driver.run(start_iteration=start_iteration) except GeoAppsError as error: logging.getLogger(__name__).warning( "\n\nApplicationError: %s\n\n", error @@ -533,17 +559,20 @@ def start(cls, filepath: str | Path | BaseUIJson, mode="r+", **kwargs) -> Self: return driver @classmethod - def start_dask_run( - cls, json_path: Path, n_workers: int | None = None, n_threads: int | None = None - ): + def start_dask_run(cls, json_path: Path, **kwargs): """ Sets Dask config settings. :param json_path: Path to input file (.ui.json) for the application. - :param n_workers: Number of workers to use. - :param n_threads: Number of threads to use. + :param kwargs: Additional keyword arguments for the dask run. + """ + start_dask_run(cls, json_path, **kwargs) + + @abstractmethod + def warm_start(self, start_iteration: int = -1): + """ + Re-start the process where it left off """ - start_dask_run(cls, json_path, n_workers=n_workers, n_threads=n_threads) @property def workers(self) -> list[tuple[str]]: @@ -583,6 +612,11 @@ def start_message(self): if self.logger: self.logger.write("Running the forward simulation ...\n") + def warm_start(self, start_iteration: int = -1): + """ + Re-start the process where it left off + """ + class InversionDriver(BaseDriver): _params_class = BaseForwardOptions | BaseInversionOptions @@ -814,6 +848,116 @@ def start_message(self): f"with chifact = {self.params.irls.starting_chi_factor})\n" ) + def warm_start(self, start_iteration: int = -1): + """ + Re-start the process where it left off + + :param start_iteration: Iteration number to start back at. + """ + log_file = next( + child for child in self.out_group.children if child.name.endswith(".log") + ) + log_file.save_file(path=self.workspace.h5file.parent, name=log_file.name) + out_file = next( + child for child in self.out_group.children if child.name.endswith(".out") + ) + out_file.save_file(path=self.workspace.h5file.parent, name=out_file.name) + out_array = read_csv(BytesIO(out_file.file_bytes), sep=" ") + + last_iter = out_array["iteration"].iloc[start_iteration] + last_beta = out_array["beta"].iloc[start_iteration] + + if self.logger: + self.logger.write( + "\n\t\t###################################################\n" + + f"\t\t\tRe-starting inversion at iteration {last_iter}\n" + + f"\t\t\t\t{self.logger.start_date_time}\n" + + "\t\t###################################################\n" + ) + + self._reset_on_iteration(last_iter) + self.inverse_problem.beta = last_beta + + def _reset_on_iteration(self, start_iteration: int): + """ + Reset the inversion parameters to a given iteration and beta value. + + Assumes that the workspace is already opened to access data. + + :param start_iteration: Iteration number to start back at. + """ + + self._reset_models(start_iteration) + + mesh = first_child_of_type(self.out_group, (DrapeModel, Octree)) + self._inversion_mesh = InversionMesh(self.workspace, self.params, entity=mesh) + self.models.active_cells = ( + self._inversion_mesh.permutation @ mesh.get_entity("active_cells")[0].values + ).astype(bool) + + self.optimization.iter = start_iteration + self._reset_directives(start_iteration) + + def _reset_directives(self, iteration: int): + """ + Reset the inversion directives based on specified iteration and model. + + :param iteration: The iteration number to reset directives for. + """ + chi_data = [ + child + for child in self.out_group.children + if child.name.endswith(".chi") and isinstance(child, FilenameData) + ] + + if chi_data: + chi_array = np.loadtxt(BytesIO(chi_data[0].file_bytes), skiprows=1) + + if self.directives.scale_misfits is not None: + self.directives.scale_misfits.scalings = chi_array[iteration, 1:] + self.directives.scale_misfits.multipliers = np.asarray( + self.data_misfit.multipliers + ) + self.data_misfit.multipliers *= self.directives.scale_misfits.scalings + + # Hard-wire beta and remove estimator directive + directive = self.directives.beta_estimate_by_eigenvalues_directive + if directive is not None and directive in self.directives.directive_list: + self.directives.directive_list.remove( + self.directives.beta_estimate_by_eigenvalues_directive + ) + + def _reset_models(self, iteration: int): + """ + Reset the inversion models based on specified iteration and mesh. + + :param iteration: The iteration number to reset the models for. + """ + mesh = first_child_of_type(self.out_group, (DrapeModel, Octree)) + flag = f"Iteration_{iteration}_" + for child in mesh.children: + if flag in child.name: + self.params.models.starting_model = child + return + + raise GeoAppsError( + f"Could not reset the inversion at iteration {iteration}, no model found." + ) + + +def first_child_of_type(entity, child_type: type | tuple): + """ + Get the first child of a given type from an entity. + + :param entity: The parent entity to search for children. + :param child_type: The type of child to find. + :return: The first child of the specified type. + """ + for child in entity.children: + if isinstance(child, child_type): + return child + raise GeoAppsError(f"No child of type {child_type} found in {entity.name}.") + class InversionLogger: """ @@ -897,11 +1041,9 @@ def validate_workers(client, workers: list[tuple[str]] | None) -> list[tuple[str if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() + file, args = argument_parser() - # TODO - Deprecate in favor of run_command to direct module - # Need to know the driver class before starting dask input_file = load_ui_json_as_dict(file) driver_class = driver_class_from_dict(input_file) - driver_class.start_dask_run(file) + driver_class.start_dask_run(file, **args) diff --git a/simpeg_drivers/electricals/direct_current/three_dimensions/forward.py b/simpeg_drivers/electricals/direct_current/three_dimensions/forward.py index c6f0b9e73..0663b7173 100644 --- a/simpeg_drivers/electricals/direct_current/three_dimensions/forward.py +++ b/simpeg_drivers/electricals/direct_current/three_dimensions/forward.py @@ -11,13 +11,11 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import ForwardDriver from simpeg_drivers.electricals.direct_current.three_dimensions.options import ( DC3DForwardOptions, ) +from simpeg_drivers.utils.utils import argument_parser class DC3DForwardDriver(ForwardDriver): @@ -27,5 +25,5 @@ class DC3DForwardDriver(ForwardDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - DC3DForwardDriver.start_dask_run(file) + file, args = argument_parser() + DC3DForwardDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/electricals/direct_current/three_dimensions/inversion.py b/simpeg_drivers/electricals/direct_current/three_dimensions/inversion.py index a112a699e..b9d66736a 100644 --- a/simpeg_drivers/electricals/direct_current/three_dimensions/inversion.py +++ b/simpeg_drivers/electricals/direct_current/three_dimensions/inversion.py @@ -11,13 +11,11 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import InversionDriver from simpeg_drivers.electricals.direct_current.three_dimensions.options import ( DC3DInversionOptions, ) +from simpeg_drivers.utils.utils import argument_parser class DC3DInversionDriver(InversionDriver): @@ -27,5 +25,5 @@ class DC3DInversionDriver(InversionDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - DC3DInversionDriver.start_dask_run(file) + file, args = argument_parser() + DC3DInversionDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/electricals/direct_current/two_dimensions/forward.py b/simpeg_drivers/electricals/direct_current/two_dimensions/forward.py index 4dfacf044..e06ff47d6 100644 --- a/simpeg_drivers/electricals/direct_current/two_dimensions/forward.py +++ b/simpeg_drivers/electricals/direct_current/two_dimensions/forward.py @@ -11,14 +11,12 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import ForwardDriver from simpeg_drivers.electricals.base_2d import Base2DDriver from simpeg_drivers.electricals.direct_current.two_dimensions.options import ( DC2DForwardOptions, ) +from simpeg_drivers.utils.utils import argument_parser class DC2DForwardDriver(ForwardDriver, Base2DDriver): @@ -28,5 +26,5 @@ class DC2DForwardDriver(ForwardDriver, Base2DDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - DC2DForwardDriver.start_dask_run(file) + file, args = argument_parser() + DC2DForwardDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/electricals/direct_current/two_dimensions/inversion.py b/simpeg_drivers/electricals/direct_current/two_dimensions/inversion.py index 01e6e93c2..179010272 100644 --- a/simpeg_drivers/electricals/direct_current/two_dimensions/inversion.py +++ b/simpeg_drivers/electricals/direct_current/two_dimensions/inversion.py @@ -11,14 +11,12 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import InversionDriver from simpeg_drivers.electricals.base_2d import Base2DDriver from simpeg_drivers.electricals.direct_current.two_dimensions.options import ( DC2DInversionOptions, ) +from simpeg_drivers.utils.utils import argument_parser class DC2DInversionDriver(Base2DDriver, InversionDriver): @@ -28,5 +26,5 @@ class DC2DInversionDriver(Base2DDriver, InversionDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - DC2DInversionDriver.start_dask_run(file) + file, args = argument_parser() + DC2DInversionDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/electricals/induced_polarization/three_dimensions/forward.py b/simpeg_drivers/electricals/induced_polarization/three_dimensions/forward.py index ea19c9621..4e0db045f 100644 --- a/simpeg_drivers/electricals/induced_polarization/three_dimensions/forward.py +++ b/simpeg_drivers/electricals/induced_polarization/three_dimensions/forward.py @@ -11,13 +11,11 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import ForwardDriver from simpeg_drivers.electricals.induced_polarization.three_dimensions.options import ( IP3DForwardOptions, ) +from simpeg_drivers.utils.utils import argument_parser class IP3DForwardDriver(ForwardDriver): @@ -27,5 +25,5 @@ class IP3DForwardDriver(ForwardDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - IP3DForwardDriver.start_dask_run(file) + file, args = argument_parser() + IP3DForwardDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/electricals/induced_polarization/three_dimensions/inversion.py b/simpeg_drivers/electricals/induced_polarization/three_dimensions/inversion.py index 97c460b62..005bc72d4 100644 --- a/simpeg_drivers/electricals/induced_polarization/three_dimensions/inversion.py +++ b/simpeg_drivers/electricals/induced_polarization/three_dimensions/inversion.py @@ -11,13 +11,11 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import InversionDriver from simpeg_drivers.electricals.induced_polarization.three_dimensions.options import ( IP3DInversionOptions, ) +from simpeg_drivers.utils.utils import argument_parser class IP3DInversionDriver(InversionDriver): @@ -27,5 +25,5 @@ class IP3DInversionDriver(InversionDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - IP3DInversionDriver.start_dask_run(file) + file, args = argument_parser() + IP3DInversionDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/electricals/induced_polarization/two_dimensions/forward.py b/simpeg_drivers/electricals/induced_polarization/two_dimensions/forward.py index c5cba460c..d7ed596d4 100644 --- a/simpeg_drivers/electricals/induced_polarization/two_dimensions/forward.py +++ b/simpeg_drivers/electricals/induced_polarization/two_dimensions/forward.py @@ -11,14 +11,12 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import ForwardDriver from simpeg_drivers.electricals.base_2d import Base2DDriver from simpeg_drivers.electricals.induced_polarization.two_dimensions.options import ( IP2DForwardOptions, ) +from simpeg_drivers.utils.utils import argument_parser class IP2DForwardDriver(ForwardDriver, Base2DDriver): @@ -28,5 +26,5 @@ class IP2DForwardDriver(ForwardDriver, Base2DDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - IP2DForwardDriver.start_dask_run(file) + file, args = argument_parser() + IP2DForwardDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/electricals/induced_polarization/two_dimensions/inversion.py b/simpeg_drivers/electricals/induced_polarization/two_dimensions/inversion.py index 7e59ae809..15f73108a 100644 --- a/simpeg_drivers/electricals/induced_polarization/two_dimensions/inversion.py +++ b/simpeg_drivers/electricals/induced_polarization/two_dimensions/inversion.py @@ -11,14 +11,12 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import InversionDriver from simpeg_drivers.electricals.base_2d import Base2DDriver from simpeg_drivers.electricals.induced_polarization.two_dimensions.options import ( IP2DInversionOptions, ) +from simpeg_drivers.utils.utils import argument_parser class IP2DInversionDriver(Base2DDriver, InversionDriver): @@ -28,5 +26,5 @@ class IP2DInversionDriver(Base2DDriver, InversionDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - IP2DInversionDriver.start_dask_run(file) + file, args = argument_parser() + IP2DInversionDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/electromagnetics/frequency_domain/forward.py b/simpeg_drivers/electromagnetics/frequency_domain/forward.py index dd539d7af..1e8bf6a5e 100644 --- a/simpeg_drivers/electromagnetics/frequency_domain/forward.py +++ b/simpeg_drivers/electromagnetics/frequency_domain/forward.py @@ -11,11 +11,9 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import ForwardDriver from simpeg_drivers.electromagnetics.frequency_domain.options import FDEMForwardOptions +from simpeg_drivers.utils.utils import argument_parser class FDEMForwardDriver(ForwardDriver): @@ -28,5 +26,5 @@ def __init__(self, params: FDEMForwardOptions): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - FDEMForwardDriver.start_dask_run(file) + file, args = argument_parser() + FDEMForwardDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/electromagnetics/frequency_domain/inversion.py b/simpeg_drivers/electromagnetics/frequency_domain/inversion.py index f83fd0375..ef4a3b54f 100644 --- a/simpeg_drivers/electromagnetics/frequency_domain/inversion.py +++ b/simpeg_drivers/electromagnetics/frequency_domain/inversion.py @@ -11,13 +11,11 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import InversionDriver from simpeg_drivers.electromagnetics.frequency_domain.options import ( FDEMInversionOptions, ) +from simpeg_drivers.utils.utils import argument_parser class FDEMInversionDriver(InversionDriver): @@ -27,5 +25,5 @@ class FDEMInversionDriver(InversionDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - FDEMInversionDriver.start_dask_run(file) + file, args = argument_parser() + FDEMInversionDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/electromagnetics/frequency_domain_1d/forward.py b/simpeg_drivers/electromagnetics/frequency_domain_1d/forward.py index 4c7f6af60..d459efa6f 100644 --- a/simpeg_drivers/electromagnetics/frequency_domain_1d/forward.py +++ b/simpeg_drivers/electromagnetics/frequency_domain_1d/forward.py @@ -11,14 +11,12 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import ForwardDriver from simpeg_drivers.electromagnetics.base_1d_driver import Base1DDriver from simpeg_drivers.electromagnetics.frequency_domain_1d.options import ( FDEM1DForwardOptions, ) +from simpeg_drivers.utils.utils import argument_parser class FDEM1DForwardDriver(ForwardDriver, Base1DDriver): @@ -28,5 +26,5 @@ class FDEM1DForwardDriver(ForwardDriver, Base1DDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - FDEM1DForwardDriver.start_dask_run(file) + file, args = argument_parser() + FDEM1DForwardDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/electromagnetics/frequency_domain_1d/inversion.py b/simpeg_drivers/electromagnetics/frequency_domain_1d/inversion.py index 3bfdb636e..f825bcc76 100644 --- a/simpeg_drivers/electromagnetics/frequency_domain_1d/inversion.py +++ b/simpeg_drivers/electromagnetics/frequency_domain_1d/inversion.py @@ -11,14 +11,12 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import InversionDriver from simpeg_drivers.electromagnetics.base_1d_driver import Base1DDriver from simpeg_drivers.electromagnetics.frequency_domain_1d.options import ( FDEM1DInversionOptions, ) +from simpeg_drivers.utils.utils import argument_parser class FDEM1DInversionDriver(InversionDriver, Base1DDriver): @@ -28,5 +26,5 @@ class FDEM1DInversionDriver(InversionDriver, Base1DDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - FDEM1DInversionDriver.start_dask_run(file) + file, args = argument_parser() + FDEM1DInversionDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/electromagnetics/time_domain/forward.py b/simpeg_drivers/electromagnetics/time_domain/forward.py index 1d2f7b142..9183535f7 100644 --- a/simpeg_drivers/electromagnetics/time_domain/forward.py +++ b/simpeg_drivers/electromagnetics/time_domain/forward.py @@ -11,11 +11,9 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import ForwardDriver from simpeg_drivers.electromagnetics.time_domain.options import TDEMForwardOptions +from simpeg_drivers.utils.utils import argument_parser class TDEMForwardDriver(ForwardDriver): @@ -25,5 +23,5 @@ class TDEMForwardDriver(ForwardDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - TDEMForwardDriver.start_dask_run(file) + file, args = argument_parser() + TDEMForwardDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/electromagnetics/time_domain/inversion.py b/simpeg_drivers/electromagnetics/time_domain/inversion.py index 9b45cedde..161c9a5ca 100644 --- a/simpeg_drivers/electromagnetics/time_domain/inversion.py +++ b/simpeg_drivers/electromagnetics/time_domain/inversion.py @@ -11,11 +11,9 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import InversionDriver from simpeg_drivers.electromagnetics.time_domain.options import TDEMInversionOptions +from simpeg_drivers.utils.utils import argument_parser class TDEMInversionDriver(InversionDriver): @@ -25,5 +23,5 @@ class TDEMInversionDriver(InversionDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - TDEMInversionDriver.start_dask_run(file) + file, args = argument_parser() + TDEMInversionDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/electromagnetics/time_domain_1d/forward.py b/simpeg_drivers/electromagnetics/time_domain_1d/forward.py index bf790bae3..3dda6029a 100644 --- a/simpeg_drivers/electromagnetics/time_domain_1d/forward.py +++ b/simpeg_drivers/electromagnetics/time_domain_1d/forward.py @@ -11,12 +11,10 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import ForwardDriver from simpeg_drivers.electromagnetics.base_1d_driver import Base1DDriver from simpeg_drivers.electromagnetics.time_domain_1d.options import TDEM1DForwardOptions +from simpeg_drivers.utils.utils import argument_parser class TDEM1DForwardDriver(ForwardDriver, Base1DDriver): @@ -26,5 +24,5 @@ class TDEM1DForwardDriver(ForwardDriver, Base1DDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - TDEM1DForwardDriver.start_dask_run(file) + file, args = argument_parser() + TDEM1DForwardDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/electromagnetics/time_domain_1d/inversion.py b/simpeg_drivers/electromagnetics/time_domain_1d/inversion.py index 6835d9801..104bacb6b 100644 --- a/simpeg_drivers/electromagnetics/time_domain_1d/inversion.py +++ b/simpeg_drivers/electromagnetics/time_domain_1d/inversion.py @@ -11,14 +11,12 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import InversionDriver from simpeg_drivers.electromagnetics.base_1d_driver import Base1DDriver from simpeg_drivers.electromagnetics.time_domain_1d.options import ( TDEM1DInversionOptions, ) +from simpeg_drivers.utils.utils import argument_parser class TDEM1DInversionDriver(InversionDriver, Base1DDriver): @@ -28,5 +26,5 @@ class TDEM1DInversionDriver(InversionDriver, Base1DDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - TDEM1DInversionDriver.start_dask_run(file) + file, args = argument_parser() + TDEM1DInversionDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/joint/driver.py b/simpeg_drivers/joint/driver.py index 3887a7bff..eac6b4502 100644 --- a/simpeg_drivers/joint/driver.py +++ b/simpeg_drivers/joint/driver.py @@ -18,7 +18,8 @@ import numpy as np import simpeg.dask.objective_function as dask_objective_function -from geoh5py.groups.property_group_type import GroupTypeEnum +from geoh5py.groups import SimPEGGroup +from geoh5py.objects import DrapeModel, Octree from geoh5py.shared.utils import fetch_active_workspace from grid_apps.utils import ( collocate_octrees, @@ -34,7 +35,8 @@ DirectivesFactory, SaveModelGeoh5Factory, ) -from simpeg_drivers.driver import InversionDriver +from simpeg_drivers.components.meshes import InversionMesh +from simpeg_drivers.driver import InversionDriver, first_child_of_type from simpeg_drivers.joint.options import BaseJointOptions from simpeg_drivers.options import ModelTypeEnum from simpeg_drivers.utils.utils import simpeg_group_to_driver @@ -48,6 +50,7 @@ def __init__(self, params: BaseJointOptions): self._directives = None self._drivers = None self._wires = None + self._is_initialized = False super().__init__(params) @@ -135,6 +138,9 @@ def get_nested_tiles(self): def initialize(self): """Generate sub drivers.""" + if self._is_initialized: + return + self.validate_create_mesh() # Add re-projection to the global mesh @@ -184,6 +190,7 @@ def initialize(self): driver.data_misfit.multipliers = multipliers self.validate_create_models() + self._is_initialized = True @property def inversion_data(self): @@ -243,6 +250,11 @@ def n_values(self): return self._n_values + def simpeg_run(self): + """Run inversion from params""" + self.initialize() + self.inversion.run(self.models.starting_model) + def validate_create_mesh(self): """Function to validate and create the inversion mesh.""" @@ -315,6 +327,43 @@ def validate_create_models(self): if model is not None: getattr(self.models, f"_{model_type}").model = model + def _reset_models(self, iteration: int): + """ + Reset the inversion models based on specified iteration and mesh. + + :param iteration: The iteration number to reset the models for. + """ + drivers = [] + # Create sub-drivers + for group in self.out_group.children: + if not isinstance(group, SimPEGGroup): + continue + + driver = simpeg_group_to_driver(group, self.workspace) + driver._reset_models(iteration) # pylint: disable=protected-access + drivers.append(driver) + + self._drivers = drivers + + def _reset_on_iteration(self, start_iteration: int): + """ + Reset the inversion parameters to a given iteration and beta value. + + Assumes that the workspace is already opened to access data. + + :param start_iteration: Iteration number to start back at. + """ + + self._reset_models(start_iteration) + + mesh = first_child_of_type(self.out_group, (DrapeModel, Octree)) + self._inversion_mesh = InversionMesh(self.workspace, self.params, entity=mesh) + + self.initialize() + + self.optimization.iter = start_iteration + self._reset_directives(start_iteration) + @staticmethod def _validate_model_consistency(model: list[None | Any], model_type: str): """ diff --git a/simpeg_drivers/joint/joint_cross_gradient/driver.py b/simpeg_drivers/joint/joint_cross_gradient/driver.py index 37b009ae8..512b916b8 100644 --- a/simpeg_drivers/joint/joint_cross_gradient/driver.py +++ b/simpeg_drivers/joint/joint_cross_gradient/driver.py @@ -13,17 +13,15 @@ from __future__ import annotations -import sys from itertools import combinations -from pathlib import Path -from geoh5py.shared.utils import fetch_active_workspace from simpeg import directives, maps from simpeg.objective_function import ComboObjectiveFunction from simpeg.regularization import CrossGradient from simpeg_drivers.joint.driver import BaseJointDriver from simpeg_drivers.joint.joint_cross_gradient.options import JointCrossGradientOptions +from simpeg_drivers.utils.utils import argument_parser class JointCrossGradientDriver(BaseJointDriver): @@ -35,9 +33,6 @@ def __init__(self, params: JointCrossGradientOptions): super().__init__(params) - with fetch_active_workspace(self.workspace, mode="r+"): - self.initialize() - def get_regularization(self): """ Create a flat ComboObjectiveFunction from all drivers provided and @@ -102,10 +97,11 @@ def _get_joint_directives(self) -> list[directives.Directive]: """ Create a list of directives for the joint inversion. """ + regularization = self.regularization # Pre-compute the regularization directives_list = super()._get_joint_directives() if self.params.iterative_rescaling: - for reg in self.regularization.objfcts: + for reg in regularization.objfcts: if isinstance(reg, CrossGradient): directives_list.append( directives.ScaleMaximumDerivatives(reg) @@ -113,7 +109,19 @@ def _get_joint_directives(self) -> list[directives.Directive]: return directives_list + def _reset_directives(self, iteration: int): + """ + Reset the inversion parameters to a given iteration and beta value. + + Assumes that the workspace is already opened to access data. + """ + super()._reset_directives(iteration) + + for directive in self.directives.directive_list: + if isinstance(directive, directives.ScaleMaximumDerivatives): + directive.scale_on_current_model(self.models.starting_model) + if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - JointCrossGradientDriver.start_dask_run(file) + file, args = argument_parser() + JointCrossGradientDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/joint/joint_petrophysics/driver.py b/simpeg_drivers/joint/joint_petrophysics/driver.py index 437d0fb92..b2c352750 100644 --- a/simpeg_drivers/joint/joint_petrophysics/driver.py +++ b/simpeg_drivers/joint/joint_petrophysics/driver.py @@ -10,9 +10,7 @@ from __future__ import annotations -import sys from functools import cached_property -from pathlib import Path import numpy as np from geoapps_utils.utils.importing import GeoAppsError @@ -23,6 +21,7 @@ from simpeg_drivers.joint.driver import BaseJointDriver from simpeg_drivers.joint.joint_petrophysics.options import JointPetrophysicsOptions +from simpeg_drivers.utils.utils import argument_parser class JointPetrophysicsDriver(BaseJointDriver): @@ -36,9 +35,6 @@ def __init__(self, params: JointPetrophysicsOptions): super().__init__(params) - with fetch_active_workspace(self.workspace, mode="r+"): - self.initialize() - @property def directives(self): if getattr(self, "_directives", None) is None and not self.params.forward_only: @@ -54,25 +50,6 @@ def directives(self): ) ) - # TODO: To bring back once we let the classification change - # directives_list.append( - # directives.SavePGIModel( - # self.inversion_mesh.entity, - # self.pgi_regularization, - # self.geo_units, - # [driver.params.physical_property for driver in self.drivers], - # transforms=[ - # lambda x: np.r_[list(self.geo_units)][self.class_mapping][ - # x.astype(int) - # ], - # maps.InjectActiveCells( - # self.inversion_mesh.mesh, self.models.active_cells, 0 - # ), - # ], - # reference_type=self.params.models.petrophysical_model.entity_type, - # ) - # ) - self._directives.directive_list = directives_list return self._directives @@ -100,8 +77,6 @@ def gaussian_model(self): covariance_type="diag", random_state=1, ) - # TODO: Use the corresponding data_maps from the reference data - # when available to define the means and covariances and weights self._gaussian_model.means_ = self.means[self.class_mapping] # set phys. prop covariances for each unit self._gaussian_model.covariances_ = self.covariances[self.class_mapping] @@ -150,8 +125,6 @@ def membership(self) -> np.ndarray: def means(self) -> np.ndarray: """ Means of the Gaussian mixture model. - - TODO: Set the means based on the model units when made available. """ means = [] for mapping in self.mapping: @@ -176,8 +149,6 @@ def means(self) -> np.ndarray: def covariances(self) -> np.ndarray: """ Covariances of the Gaussian mixture model. - - TODO: Set the covariances based on the model units when made available. """ return np.ones((self.n_units, len(self.mapping))) @@ -185,8 +156,6 @@ def covariances(self) -> np.ndarray: def weights(self) -> np.ndarray: """ Weights of the Gaussian mixture model. - - TODO: Set the weights based on the model units when made available. """ weights = [] volumes = self.inversion_mesh.mesh.cell_volumes[self.models.active_cells] @@ -236,5 +205,5 @@ def _overload_regularization(self, regularization: ComboObjectiveFunction): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - JointPetrophysicsDriver.start_dask_run(file) + file, args = argument_parser() + JointPetrophysicsDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/joint/joint_surveys/driver.py b/simpeg_drivers/joint/joint_surveys/driver.py index 0b6eb812f..b822bd911 100644 --- a/simpeg_drivers/joint/joint_surveys/driver.py +++ b/simpeg_drivers/joint/joint_surveys/driver.py @@ -11,18 +11,18 @@ from __future__ import annotations -import sys from logging import getLogger -from pathlib import Path import numpy as np +from geoh5py.objects import DrapeModel, Octree from geoh5py.shared.utils import fetch_active_workspace from simpeg import directives, maps -from simpeg_drivers.driver import InversionDriver +from simpeg_drivers.driver import InversionDriver, first_child_of_type from simpeg_drivers.joint.driver import BaseJointDriver from simpeg_drivers.joint.joint_surveys.options import JointSurveysOptions from simpeg_drivers.options import ModelTypeEnum +from simpeg_drivers.utils.utils import argument_parser logger = getLogger(__name__) @@ -33,12 +33,6 @@ class JointSurveysDriver(BaseJointDriver): _params_class = JointSurveysOptions - def __init__(self, params: JointSurveysOptions): - super().__init__(params) - - with fetch_active_workspace(self.workspace, mode="r+"): - self.initialize() - def get_regularization(self): """ Overload the regularization using the method of the first driver. @@ -156,10 +150,25 @@ def directives(self): return self._directives + def _reset_models(self, iteration: int): + """ + Reset the inversion models based on specified iteration. + + :param iteration: The iteration number to reset the models for. + """ + mesh = first_child_of_type(self.out_group, (DrapeModel, Octree)) + flag = f"Iteration_{iteration}_" + for child in mesh.children: + if flag in child.name: + self.params.models.starting_model = child + break + + super()._reset_models(iteration) + JointSurveysDriver.n_values = InversionDriver.n_values JointSurveysDriver.mapping = InversionDriver.mapping if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - JointSurveysDriver.start_dask_run(file) + file, args = argument_parser() + JointSurveysDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/natural_sources/apparent_conductivity/forward.py b/simpeg_drivers/natural_sources/apparent_conductivity/forward.py index 2293e6112..83d073fac 100644 --- a/simpeg_drivers/natural_sources/apparent_conductivity/forward.py +++ b/simpeg_drivers/natural_sources/apparent_conductivity/forward.py @@ -11,13 +11,11 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import ForwardDriver from simpeg_drivers.natural_sources.apparent_conductivity.options import ( AppConForwardOptions, ) +from simpeg_drivers.utils.utils import argument_parser class AppConForwardDriver(ForwardDriver): @@ -27,5 +25,5 @@ class AppConForwardDriver(ForwardDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - AppConForwardDriver.start_dask_run(file) + file, args = argument_parser() + AppConForwardDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/natural_sources/apparent_conductivity/inversion.py b/simpeg_drivers/natural_sources/apparent_conductivity/inversion.py index ec66ac411..c4517bc98 100644 --- a/simpeg_drivers/natural_sources/apparent_conductivity/inversion.py +++ b/simpeg_drivers/natural_sources/apparent_conductivity/inversion.py @@ -11,13 +11,11 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import InversionDriver from simpeg_drivers.natural_sources.apparent_conductivity.options import ( AppConInversionOptions, ) +from simpeg_drivers.utils.utils import argument_parser class AppConInversionDriver(InversionDriver): @@ -27,5 +25,5 @@ class AppConInversionDriver(InversionDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - AppConInversionDriver.start_dask_run(file) + file, args = argument_parser() + AppConInversionDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/natural_sources/magnetotellurics/forward.py b/simpeg_drivers/natural_sources/magnetotellurics/forward.py index f4f9e8475..548a4933d 100644 --- a/simpeg_drivers/natural_sources/magnetotellurics/forward.py +++ b/simpeg_drivers/natural_sources/magnetotellurics/forward.py @@ -11,11 +11,9 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import ForwardDriver from simpeg_drivers.natural_sources.magnetotellurics.options import MTForwardOptions +from simpeg_drivers.utils.utils import argument_parser class MTForwardDriver(ForwardDriver): @@ -25,5 +23,5 @@ class MTForwardDriver(ForwardDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - MTForwardDriver.start_dask_run(file) + file, args = argument_parser() + MTForwardDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/natural_sources/magnetotellurics/inversion.py b/simpeg_drivers/natural_sources/magnetotellurics/inversion.py index 3185993c9..55e485a08 100644 --- a/simpeg_drivers/natural_sources/magnetotellurics/inversion.py +++ b/simpeg_drivers/natural_sources/magnetotellurics/inversion.py @@ -11,11 +11,9 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import InversionDriver from simpeg_drivers.natural_sources.magnetotellurics.options import MTInversionOptions +from simpeg_drivers.utils.utils import argument_parser class MTInversionDriver(InversionDriver): @@ -25,5 +23,5 @@ class MTInversionDriver(InversionDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - MTInversionDriver.start_dask_run(file) + file, args = argument_parser() + MTInversionDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/natural_sources/tipper/forward.py b/simpeg_drivers/natural_sources/tipper/forward.py index 14280e395..bff115ec0 100644 --- a/simpeg_drivers/natural_sources/tipper/forward.py +++ b/simpeg_drivers/natural_sources/tipper/forward.py @@ -11,11 +11,9 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import ForwardDriver from simpeg_drivers.natural_sources.tipper.options import TipperForwardOptions +from simpeg_drivers.utils.utils import argument_parser class TipperForwardDriver(ForwardDriver): @@ -25,5 +23,5 @@ class TipperForwardDriver(ForwardDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - TipperForwardDriver.start_dask_run(file) + file, args = argument_parser() + TipperForwardDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/natural_sources/tipper/inversion.py b/simpeg_drivers/natural_sources/tipper/inversion.py index 812302556..06853bcd9 100644 --- a/simpeg_drivers/natural_sources/tipper/inversion.py +++ b/simpeg_drivers/natural_sources/tipper/inversion.py @@ -11,11 +11,9 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import InversionDriver from simpeg_drivers.natural_sources.tipper.options import TipperInversionOptions +from simpeg_drivers.utils.utils import argument_parser class TipperInversionDriver(InversionDriver): @@ -25,5 +23,5 @@ class TipperInversionDriver(InversionDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - TipperInversionDriver.start_dask_run(file) + file, args = argument_parser() + TipperInversionDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/options.py b/simpeg_drivers/options.py index d3e070fca..06270d485 100644 --- a/simpeg_drivers/options.py +++ b/simpeg_drivers/options.py @@ -171,9 +171,6 @@ class CoreOptions(Options): :param generate_sweep: Generate sweep file instead of running the app. """ - # TODO: Refactor to allow frozen True. Currently params.data_object is - # updated after z_from_topo applied in entity_factory.py. See - # simpeg_drivers/components/data.py ln. 127 model_config = ConfigDict( frozen=False, arbitrary_types_allowed=True, @@ -240,9 +237,6 @@ def padding_cells(self) -> int: def write_ui_json(self, path: Path) -> Path: """ Write UI JSON file. - - TODO: Replace in favor of base Options implementation - after geoapps_utils@feature/uijson is merged """ ui_json = SimPEGDriversUIJson.read(self.default_ui_json) diff --git a/simpeg_drivers/plate_simulation/driver.py b/simpeg_drivers/plate_simulation/driver.py index afbddfe41..c5457b1de 100644 --- a/simpeg_drivers/plate_simulation/driver.py +++ b/simpeg_drivers/plate_simulation/driver.py @@ -11,6 +11,7 @@ from __future__ import annotations import sys +from copy import deepcopy from pathlib import Path import numpy as np @@ -21,6 +22,7 @@ from geoh5py.data import FloatData, ReferencedData from geoh5py.objects import Octree, Points, Surface from geoh5py.shared.utils import fetch_active_workspace +from geoh5py.ui_json.input_file import InputFile from simpeg_drivers.driver import ( InversionDriver, @@ -28,12 +30,14 @@ validate_workers, ) from simpeg_drivers.options import BaseForwardOptions, ModelTypeEnum +from simpeg_drivers.plate_simulation.leroi_air.driver import LeroiAirDriver +from simpeg_drivers.plate_simulation.leroi_air.options import LeroiAirOptions from simpeg_drivers.plate_simulation.models.events import Anomaly, Erosion, Overburden from simpeg_drivers.plate_simulation.models.series import DikeSwarm, Geology from simpeg_drivers.plate_simulation.options import PlateSimulationOptions from simpeg_drivers.utils.synthetics.meshes import get_octree_mesh from simpeg_drivers.utils.utils import ( - driver_class_from_name, + driver_class_from_dict, start_dask_run, validate_out_group, ) @@ -67,17 +71,17 @@ def __init__( 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._client: Client | bool = validate_client(client) self._workers: list[tuple[str]] = validate_workers(self._client, workers) + self._simulation_driver: InversionDriver | None = None + self.simulation_parameters: BaseForwardOptions = self._initialize_forward_opts() def run(self) -> InversionDriver: """Create octree mesh, fill model, and simulate.""" - with fetch_active_workspace(self.params.geoh5, mode="r+"): - self.simulation_driver.run() - self.update_monitoring_directory(self._out_group) + self.simulation_driver.run() + self.simulation_parameters.update_out_group_options() + self.update_monitoring_directory(self._out_group) logger.info("done.") logger.handlers.clear() @@ -87,47 +91,17 @@ def run(self) -> InversionDriver: @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 = driver_class_from_name( - self.simulation_parameters.inversion_type, forward_only=True - ) - self._simulation_driver = driver_class( - self.simulation_parameters, - client=self._client, - workers=self._workers, - ) - self._simulation_driver.out_group.parent = self._out_group + if self.params.use_leroi: + _ = self.plates # Saves MaxwellPlate(s) when no octree/model + self._simulation_driver = self._get_leroi_driver() + else: + self._simulation_driver = self._get_simpeg_driver() 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 = ( - ModelTypeEnum.resistivity - ) - return self._simulation_parameters - @property def survey(self): - if self._survey is None: - self._survey = self.simulation_parameters.data_object - - return self._survey + return self.simulation_parameters.data_object @property def topography(self) -> Surface | Points: @@ -156,23 +130,10 @@ def plates(self) -> list[Plate]: self.params.model.plate_options.spacing, self.params.model.plate_options.geometry.direction, ) - return self._plates - - @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() + for plate in self._plates: + plate.to_maxwell_plate(self.params.geoh5, parent=self._out_group) - return self._model + return self._plates def make_mesh(self) -> Octree: """ @@ -182,19 +143,17 @@ def make_mesh(self) -> Octree: """ logger.info("making the mesh...") - with fetch_active_workspace(self.params.geoh5, mode="r+") as geoh5: - surfaces = [p.surface(geoh5) for p in self.plates] - mesh = get_octree_mesh( - opts=self.params.mesh, - survey=self.survey, - topography=self.simulation_parameters.active_cells.topography_object, - plates=surfaces, - name=self.params.mesh.name, - ) - - mesh.parent = self._out_group + surfaces = [p.surface(self.params.geoh5) for p in self.plates] + self._mesh = get_octree_mesh( + opts=self.params.mesh, + survey=self.survey, + topography=self.simulation_parameters.active_cells.topography_object, + plates=surfaces, + name="Octree", + ) + self._mesh.parent = self._out_group - return mesh + return self._mesh def make_model(self) -> FloatData: """Create background + plate and overburden model from parameters.""" @@ -221,7 +180,7 @@ def make_model(self) -> FloatData: scenario = Geology( workspace=self.params.geoh5, - mesh=self.mesh, + mesh=self.simulation_parameters.mesh, background=self.params.model.background, history=[dikes, overburden, erosion], ) @@ -234,7 +193,7 @@ def make_model(self) -> FloatData: if physical_property == "conductivity": physical_property = "resistivity" - model = self.mesh.add_data( + model = self._mesh.add_data( { "geology": { "type": "referenced", @@ -250,7 +209,7 @@ def make_model(self) -> FloatData: for k, v in physical_property_map.items(): starting_model_values[geology == k] = v - starting_model = self.mesh.add_data( + starting_model = self.simulation_parameters.mesh.add_data( {"starting_model": {"values": starting_model_values}} ) @@ -309,6 +268,71 @@ def start_dask_run( """ start_dask_run(cls, json_path, n_workers=n_workers, n_threads=n_threads) + def _get_simpeg_driver(self): + + 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'." + ) + + driver_class = driver_class_from_dict(self.simulation_parameters.__dict__) + self.simulation_parameters.mesh = self.make_mesh() + self.simulation_parameters.models.starting_model = self.make_model() + self._simulation_driver = driver_class( + self.simulation_parameters, + client=self._client, + workers=self._workers, + ) + + return self._simulation_driver + + def _get_leroi_driver(self): + leroi_opts = LeroiAirOptions.from_plate_simulation_options( + self.params.model, self.simulation_parameters + ) + driver = LeroiAirDriver(leroi_opts) + return driver + + def _collect_simulation_opts(self) -> BaseForwardOptions: + """Collect template simulation options.""" + + simulation_options = deepcopy(self.params.simulation.options) + simulation_options["geoh5"] = self.params.geoh5 + + input_file = InputFile(ui_json=simulation_options, validate=False) + driver = driver_class_from_dict(input_file.data) + + return driver._params_class.build(input_file.data) # pylint: disable=protected-access + + def _initialize_forward_opts(self) -> BaseForwardOptions: + """Initialize the forward simulation options with mesh and model.""" + + opts = self._collect_simulation_opts() + + update = {} + models_update = {} + if opts.physical_property == "conductivity": + models_update["model_type"] = ModelTypeEnum.resistivity + if not self.params.use_leroi: + update["mesh"] = None + models_update["starting_model"] = None + update["models"] = opts.models.model_copy(update=models_update) + + out_group = validate_out_group(opts) + out_group = out_group.copy( + parent=self.out_group, + copy_children=False, + copy_relatives=False, + ) + update["out_group"] = out_group + forward_opts = opts.model_copy(update=update) + forward_opts.update_out_group_options() + + return forward_opts + if __name__ == "__main__": file = Path(sys.argv[1]) diff --git a/simpeg_drivers/plate_simulation/leroi_air/__init__.py b/simpeg_drivers/plate_simulation/leroi_air/__init__.py new file mode 100644 index 000000000..1b788624a --- /dev/null +++ b/simpeg_drivers/plate_simulation/leroi_air/__init__.py @@ -0,0 +1,9 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2023-2026 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/leroi_air/driver.py b/simpeg_drivers/plate_simulation/leroi_air/driver.py new file mode 100644 index 000000000..143dd2efd --- /dev/null +++ b/simpeg_drivers/plate_simulation/leroi_air/driver.py @@ -0,0 +1,59 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2023-2026 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 subprocess +from pathlib import Path + +from .interface import LeroiAirInterface +from .options import LeroiAirOptions + + +class LeroiAirDriver: + """Orchestrates a LeroiAir forward simulation from input preparation to geoh5 output.""" + + def __init__( + self, + options: LeroiAirOptions, + ) -> None: + """Initialize with simulation options.""" + self.options = options + self.interface = LeroiAirInterface(options) + + @property + def project_path(self) -> Path: + """Directory containing the geoh5 workspace file.""" + return self.options.survey.entity.workspace.h5file.parent + + def run_leroi(self) -> subprocess.CompletedProcess: + """Run the LeroiAir executable and raise on non-zero exit.""" + result = subprocess.run( + ["LeroiAir550_JR", "LeroiAir"], + cwd=self.project_path, + capture_output=True, + text=True, + check=False, + ) + if result.returncode != 0: + raise RuntimeError( + f"LeroiAir failed with return code {result.returncode}.\n" + f"stderr:\n{result.stderr}\n" + f"stdout:\n{result.stdout}" + ) + return result + + def run(self) -> None: + """Write input, run LeroiAir, and save simulated data to geoh5.""" + self.interface.input.write_cfl_file(self.project_path / "LeroiAir.cfl") + self.run_leroi() + self.interface.output.save_to_geoh5( + outfile=self.project_path / "LeroiAir.out", + out_group=self.options.out_group, + normalization=1e-9, + ) diff --git a/simpeg_drivers/plate_simulation/leroi_air/interface.py b/simpeg_drivers/plate_simulation/leroi_air/interface.py new file mode 100644 index 000000000..4d486d701 --- /dev/null +++ b/simpeg_drivers/plate_simulation/leroi_air/interface.py @@ -0,0 +1,581 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2023-2026 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 functools import cached_property +from pathlib import Path +from typing import Any, Literal + +import numpy as np +from geoh5py.groups import UIJsonGroup +from pydantic import BaseModel, ConfigDict, Field + +from .options import LeroiAirOptions, SurveyOptions + + +class LeroiAirInterface: + def __init__(self, opts: LeroiAirOptions): + self.input = LeroiAirInput(opts) + self.output = LeroiAirOutput(opts.survey) + + +class LeroiAirFields(BaseModel): + """ + Self-documenting fields for the LeroiAir .cfl control file. + + Notes: + - Aliases are LeroiAir's native .cfl parameter names; call + ``model_dump(by_alias=True)`` to produce a record-ready dict. + + - The plate reference point (PRP) is the midpoint of the plate + reference edge (PRE), which is the south edge of a pre-oriented + north-south horizontal plate. It serves as the origin for azimuth + and dip orientations. + + - All elevations and altitudes are relative levels (RL), positive + upward. PLTOP is therefore negative for below-surface plates. + + - TXON and WAVEFORM describe the positive half-cycle only; LeroiAir + constructs the full bipolar waveform by mirroring the second half as + its negative. + """ + + model_config = ConfigDict(populate_by_name=True, arbitrary_types_allowed=True) + + domain: Literal[1, 2] = Field( + alias="TDFD", description="1 for time, 2 for frequency." + ) + layered_earth_only: Literal[0, 1] = Field( + alias="DO3D", + default=1, + description="1 to model plate and background and 0 to modelbackground only.", + ) + profile: int = Field( + alias="PRFL", + default=1, + description="Output formatted as columns of time channel data.", + ) + model_validation: int = Field( + alias="ISTOP", + default=0, + description="Prompt user with model description before running. ", + ) + waveform_input_format: int = Field( + alias="ISW", + default=1, + description="Specified that the waveform is read as a column of " + "time in milliseconds (TXON) and current in amps " + "(WAVEFORM)", + ) + waveform_length: int = Field( + alias="NSX", + description="Number of samples in the waveform. LeroiAir uses " + "this field to parse the waveform in RECORD 4.", + ) + waveform_times: np.ndarray = Field( + alias="TXON", description="Time (in ms) sampling the waveform." + ) + waveform_amplitude: np.ndarray = Field( + alias="WAVEFORM", description="Amplitude (in amps) of the sampled waveform." + ) + offtime: float = Field( + alias="OFFTIME", + description="Time (in ms) between the end of the ontime pulse of the " + "first half-cycle and the negative ontime pulse of the " + "second half-cycle.", + ) + magnetic_field: Literal[0, 1] = Field( + alias="STEP", + description="LeroiAir will simulate dBdt data if 0 and B data if 1.", + ) + magnetic_field_units: Literal[1, 2, 3] = Field( + alias="UNITS", + default=1, + description="Units of the exported data. If dBdt data is requested, " + "then 1: nT/s, 2: pT/s, 3, fT/s. If B data is requested, " + "then 1: nT, 2: pT, 3, fT.", + ) + number_of_time_channels: int = Field( + alias="NCHNL", + description="Number of time channels to simulate. LeroiAir uses this " + "field to parse the time channel data in RECORD 5.", + ) + time_channel_format: int = Field( + alias="KRXW", + default=2, + description="Specifies that the time channels are provided in RECORDS " + "5 and 6 as time gate centers (TMS) and widths (WIDTH) in " + "milliseconds.", + ) + time_channel_centers: np.ndarray = Field( + alias="TMS", description="Time gate centers in milliseconds." + ) + time_channel_widths: np.ndarray = Field( + alias="WIDTH", description="Time gate widths in milliseconds." + ) + component: int = Field( + alias="CMP", + default=3, + description="Exports all 3 components (vertical, inline, crossline).", + ) + normalization: int = Field( + alias="KPPM", + default=0, + description="No normalization applied to simulated data.", + ) + transmitter_angle: float = Field( + alias="TXCLN", + default=0.0, + description="Transmitter dipole axis angle in degrees measured from " + "the vertical.", + ) + transmitter_area: float = Field( + alias="TXAREA", default=1.0, description="Assumes a unit transmitter area." + ) + transmitter_turns: int = Field( + alias="NTRN", + default=1, + description="Assumes a single turn of the transmitter wire.", + ) + receiver_vertical_offset: float = Field( + alias="ZRX0", description="Vertical Tx_Rx offset. Assumed coincident" + ) + receiver_inline_offset: float = Field( + alias="XRX0", description="In-line horizontal Tx-Rx offset. Assumed coincident." + ) + receiver_transverse_offset: float = Field( + alias="YRX0", + description="Transverse horizontal Tx-Rx offset. Assumed coincident.", + ) + number_of_stations: int = Field( + alias="NSTAT", description="Total number of receiver sites." + ) + survey_data_type: int = Field( + alias="SURVEY", + default=2, + description="Variable altitude and course with constant Tx-Rx.", + ) + altitude_format: int = Field( + alias="BAROMTRC", + default=1, + description="Altitudes are in metres above sea level.", + ) + line_tagging: int = Field( + alias="LINE_TAG", + default=0, + description="All stations tagged with default (1000).", + ) + station_easting: np.ndarray = Field( + alias="EAST", description="Transmitter easting (m)." + ) + station_northing: np.ndarray = Field( + alias="NORTH", description="Transmitter northing (m)." + ) + station_altitude: np.ndarray = Field( + alias="ALT", description="Transmitter altitude (m above sea level)." + ) + number_of_layers: int = Field( + alias="NLAYER", description="Number of layers including the basement.", gt=0 + ) + number_of_plates: int = Field( + alias="NPLATE", description="Number of thin plates in the basement.", lt=9 + ) + number_of_lithologies: int = Field( + alias="NLITH", + description="Total number of layer plus plate lithologies.", + gt=0, + ) + ground_level: float = Field( + alias="GND_LVL", + default=0.0, + description="Relative level of flat surface fixed at zero metres.", + ) + resistivity: np.ndarray = Field( + alias="RES", description="Resistivity of each lithology (ohm-m)." + ) + conductance: np.ndarray = Field( + alias="SIG_T", + description="Conductance (conductivity-thickness product) of each lithology.", + ) + magnetic_permeability: np.ndarray = Field( + alias="RMU", description="Relative magnetic permeability. No contrast assumed." + ) + dielectric_permittivity: np.ndarray = Field( + alias="REPS", + description="Relative dielectric permittivity. No contrast assumed.", + ) + chargeability: np.ndarray = Field( + alias="CHRG", description="Cole-Cole chargeability. No IP assumed." + ) + cole_cole_time_constant: np.ndarray = Field( + alias="CTAU", description="Cole-Cole time constant. No IP assumed." + ) + cole_cole_frequency_constant: np.ndarray = Field( + alias="CFREQ", description="Cole-Cole frequency constant. No IP assumed." + ) + layer_lithology: np.ndarray = Field( + alias="LITH", description="Integer lithology index assigned to each layer." + ) + layer_thickness: np.ndarray = Field( + alias="THICK", description="Thickness of each overburden layer (m)." + ) + plate_lithology: np.ndarray = Field( + alias="LITHP", description="Integer lithology index assigned to each plate." + ) + cell_width: float = Field( + alias="CELLW", description="Cell dimension for plate discretization (m)." + ) + plate_reference_point_easting: np.ndarray = Field( + alias="CNTR_East", description="Easting of the PRP (m)." + ) + plate_reference_point_northing: np.ndarray = Field( + alias="CNTR_North", description="Northing of the PRP (m)." + ) + plate_altitude: np.ndarray = Field( + alias="PLTOP", + description="Altitude relative to the ground (GND_LVL) in meter," + "negative downward.", + ) + plate_strike_length: np.ndarray = Field( + alias="PLNGTH", description="Strike length of each plate (m)." + ) + plate_dip_length: np.ndarray = Field( + alias="DPWDTH", description="Dip length of each plate (m)." + ) + plate_dip_azimuth: np.ndarray = Field( + alias="DZM", + description="Dip azimuth of each plate in degrees east of north " + "(0 ≤ DZM ≤ 180).", + ) + plate_dip: np.ndarray = Field( + alias="DIP", description="Dip angle of each plate in degrees (0 ≤ DIP < 180)." + ) + + +class LeroiAirInput: + """LeroiAir control file formatting from geoh5py objects and options.""" + + version: str = "8.0" + + def __init__(self, opts: LeroiAirOptions): + self.opts = opts + + @cached_property + def aliased_values(self) -> dict[str, Any]: + """Serves .cfl input file aliases and corresponding data to line formatter.""" + return LeroiAirFields( + domain=1 if self.opts.domain == "time" else 2, + magnetic_field=int(self.opts.step), + layered_earth_only=0 if self.opts.layered_earth_only else 1, + waveform_length=len(self.opts.survey.ontime_waveform), + waveform_times=self.opts.survey.ontime_waveform[:, 0], + waveform_amplitude=self.opts.survey.ontime_waveform[:, 1], + offtime=self.opts.survey.offtime, + output_quantity=int(self.opts.step), + number_of_time_channels=len(self.opts.survey.channels), + time_channel_centers=( + self.opts.survey.timing_mark + np.array(self.opts.survey.channels) + ), + time_channel_widths=self.opts.survey.channel_widths, + number_of_stations=self.opts.survey.n_stations, + station_easting=self.opts.survey.entity.locations[:, 0], + station_northing=self.opts.survey.entity.locations[:, 1], + station_altitude=self.opts.survey.drape_height(self.opts.topo), + receiver_vertical_offset=self.opts.survey.entity.vertical_offset or 0.0, + receiver_inline_offset=self.opts.survey.entity.inline_offset or 0.0, + receiver_transverse_offset=self.opts.survey.entity.crossline_offset or 0.0, + number_of_layers=self.opts.n_layers, + number_of_plates=self.opts.n_plates, + number_of_lithologies=self.opts.n_layers + self.opts.n_plates, + resistivity=self.opts.resistivities, + conductance=self.opts.conductivity_thicknesses, + magnetic_permeability=np.ones_like(self.opts.resistivities), + dielectric_permittivity=np.ones_like(self.opts.resistivities), + chargeability=np.zeros_like(self.opts.resistivities), + cole_cole_time_constant=np.zeros_like(self.opts.resistivities), + cole_cole_frequency_constant=np.ones_like(self.opts.resistivities), + layer_lithology=1 + np.arange(self.opts.n_layers, dtype=int), + plate_lithology=( + 1 + np.arange(self.opts.n_plates, dtype=int) + self.opts.n_layers + ), + layer_thickness=np.array(self.opts.layer_thicknesses), + cell_width=self.opts.cell_size, + plate_reference_point_easting=np.array( + [g.easting for g in self.opts.plate_geometries] + ), + plate_reference_point_northing=np.array( + [g.northing for g in self.opts.plate_geometries] + ), + plate_altitude=np.array( + [-1 * g.elevation for g in self.opts.plate_geometries] + ), + plate_strike_length=np.array( + [g.strike_length for g in self.opts.plate_geometries] + ), + plate_dip_length=np.array( + [g.dip_length for g in self.opts.plate_geometries] + ), + plate_dip_azimuth=np.array( + [g.direction for g in self.opts.plate_geometries] + ), + plate_dip=np.array([g.dip for g in self.opts.plate_geometries]), + ).model_dump(by_alias=True) + + def _format_value(self, value: int | float) -> str: + """ + Format a scalar value as an integer of bounded precision float. + + :param value: Value to format. + """ + match value: + case int() | np.integer(): + return str(int(value)) + case float() | np.floating(): + return self._format_float(value) + case _: + return str(value) + + def _format_float(self, value: float) -> str: + """ + Format a float, truncating to float_precision only when needed. + + :param value: Value to format as a bounded precision float. + """ + _, _, decimals = str(value).partition(".") + if len(decimals) > self.opts.float_precision: + return f"{value:.{self.opts.float_precision}f}" + return str(value) + + def _format_scalar_params(self, params: list[str]) -> str: + """ + Format one scalar value per param onto a single line. + + :param params: Parameter names to format a single line of scalar data. + """ + values = [self._format_value(self.aliased_values[k]) for k in params] + return f"{' '.join(values)} \t ! {', '.join(params)}" + + def _format_vector_param(self, param: str) -> str: + """ + Format all elements of a single vector param onto a single line. + + :param param: Parameter name to format as a single line of vector data. + """ + values = [self._format_value(v) for v in self.aliased_values[param]] + return f"{' '.join(values)} \t ! {param}" + + def format_line(self, params: str | list[str]) -> str: + """ + Format one or more param values on a single line. + + :param params: Parameter names to format as a single line. + """ + if isinstance(params, str): + return self._format_vector_param(params) + return self._format_scalar_params(params) + + def format_multi_line(self, params: list[str]) -> str: + """ + Format one or more vector param values as a row-per-entry table. + + :param params: Parameter names to format as a multi-line. + """ + columns = [self.aliased_values[k] for k in params] + rows = [ + " ".join(self._format_value(v) for v in row) + for row in zip(*columns, strict=True) + ] + return "\n".join(rows) + "\t ! " + ", ".join(params) + + @property + def record_2(self) -> str: + return self.format_line(["TDFD", "DO3D", "PRFL", "ISTOP"]) + + @property + def record_3(self) -> str: + return self.format_line( + ["ISW", "NSX", "STEP", "UNITS", "NCHNL", "KRXW", "OFFTIME"] + ) + + @property + def record_4(self) -> str: + return self.format_multi_line(["TXON", "WAVEFORM"]) + + @property + def record_5(self) -> str: + return self.format_line("TMS") + + @property + def record_6(self) -> str: + return self.format_line("WIDTH") + + @property + def record_7(self) -> str: + return self.format_line(["TXCLN", "CMP", "KPPM"]) + + @property + def record_7p1(self) -> str: + return self.format_line(["NPPF"]) + + @property + def record_7p2(self) -> str: + return self.format_line(["TXAREA", "NTRN"]) + + @property + def record_8(self) -> str: + return self.format_line(["ZRX0", "XRX0", "YRX0"]) + + @property + def record_9(self) -> str: + return self.format_line(["NSTAT", "SURVEY", "BAROMTRC", "LINE_TAG"]) + + @property + def record_9p1(self) -> str: + return self.format_multi_line(["EAST", "NORTH", "ALT"]) + + @property + def record_10(self) -> str: + return self.format_line(["NLAYER", "NPLATE", "NLITH", "GND_LVL"]) + + @property + def record_11(self) -> str: + return self.format_multi_line( + ["RES", "SIG_T", "RMU", "REPS", "CHRG", "CTAU", "CFREQ"] + ) + + @property + def record_12(self) -> str: + return self.format_multi_line(["LITH", "THICK"]) + + @property + def record_13(self) -> str: + return self.format_line(["CELLW"]) + + @property + def record_14(self) -> str: + return self.format_multi_line(["LITHP", "CNTR_East", "CNTR_North", "PLTOP"]) + + @property + def record_15(self) -> str: + return self.format_multi_line(["PLNGTH", "DPWDTH", "DZM", "DIP"]) + + def format_cfl_file(self) -> str: + """ + Generates lines of text for an .cfl input file to run LeroiAir. + + Collects appropriate 'Records' and adds lines to the input file one by one. + """ + lines = [] + lines.append(self.opts.title) + lines.append(self.record_2) + lines.append(self.record_3) + lines.append(self.record_4) + lines.append(self.record_5) + lines.append(self.record_6) + lines.append(self.record_7) + if self.aliased_values["KPPM"] > 0: + lines.append(self.record_7p1) + lines.append(self.record_7p2) + lines.append(self.record_8) + lines.append(self.record_9) + lines.append(self.record_9p1) + lines.append(self.record_10) + lines.append(self.record_11) + lines.append(self.record_12) + lines.append(self.record_13) + lines.append(self.record_14) + lines.append(self.record_15) + + return "\n".join(lines) + "\n" + + def write_cfl_file(self, filepath: Path) -> None: + """ + Write the formatted .cfl input file to disk. + + :param filepath: Path where the .cfl file will be written. + """ + with open(filepath, mode="w", encoding="utf-8") as f: + f.write(self.format_cfl_file()) + + +class LeroiAirOutput: + """LeroiAir output file parsing and saving to geoh5py objects/data.""" + + _COMPONENT_ANCHORS: dict[str, str] = { + "crossline": "TRANSVERSE COMPONENT", + "inline": "IN-LINE COMPONENT", + "vertical": "VERTICAL COMPONENT", + } + + def __init__(self, opts: SurveyOptions): + self.opts = opts + + def _find_data_start(self, chunk: list[str]) -> int: + """ + Return the index of the first station data row within a section chunk. + + :param chunk: Chunk of data containing a header to index lines from. + """ + header_idx = next( + i + for i, line in enumerate(chunk) + if all(k in line for k in ["EAST", "NORTH", "ALT"]) + ) + return header_idx + 2 + + def _slice_data_lines(self, lines: list[str], anchor: str) -> list[str]: + """ + Slice the station data rows that follow the given section header. + + :param lines: Lines to slice. + :param anchor: String marking the start of a chunk of output data. + """ + anchor_idx = next(i for i, line in enumerate(lines) if anchor in line) + chunk = lines[anchor_idx:] + data_start = self._find_data_start(chunk) + return chunk[data_start : data_start + self.opts.n_stations] + + def _extract_data( + self, outfile: str | Path, component: Literal["inline", "crossline", "vertical"] + ) -> np.ndarray: + """ + Extract channel data for a single component from a LeroiAir .out file. + + :param outfile: Path to the output file from a LeroiAir run. + :param component: Component to extract. + """ + lines = Path(outfile).read_text(encoding="utf-8", errors="replace").splitlines() + data_lines = self._slice_data_lines(lines, self._COMPONENT_ANCHORS[component]) + return np.array([line.split() for line in data_lines], dtype=float)[:, 4:] + + def save_to_geoh5( + self, outfile: str | Path, out_group: UIJsonGroup, normalization: float = 1 + ): + """ + Save LeroiAir simulated data on the provided survey to geoh5. + + :param outfile: Path to output file from a LeroiAir run. + :param out_group: Group where a copy of the survey will be saved + along with all the data computed by LeroiAir. + :param normalization: Normalization multiplied against the data + before saving. + """ + + survey = self.opts.entity.copy(parent=out_group, copy_children=False) + for component in "inline", "crossline", "vertical": + data = self._extract_data(outfile=outfile, component=component) + entities = survey.add_data( + { + f"fwd {component} [{i}]": {"values": data[:, i] * normalization} + for i in range(len(self.opts.channels)) + } + ) + + survey.create_property_group(name=component, properties=entities) diff --git a/simpeg_drivers/plate_simulation/leroi_air/options.py b/simpeg_drivers/plate_simulation/leroi_air/options.py new file mode 100644 index 000000000..f780bc7ab --- /dev/null +++ b/simpeg_drivers/plate_simulation/leroi_air/options.py @@ -0,0 +1,233 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2023-2026 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 typing import Literal, Self + +import numpy as np +from geoapps_utils.modelling.plates import PlateModel +from geoh5py.groups import SimPEGGroup +from geoh5py.objects.surveys.electromagnetics.airborne_tem import AirborneTEMReceivers +from pydantic import BaseModel, ConfigDict, model_validator +from scipy.interpolate import LinearNDInterpolator + +from simpeg_drivers.components.topography import InversionTopography +from simpeg_drivers.electromagnetics.time_domain.options import TDEMForwardOptions +from simpeg_drivers.plate_simulation.options import ModelOptions + + +TIME_UNIT_CONVERSION = { + "Seconds (s)": 1e3, + "Milliseconds (ms)": 1, + "Microseconds (us)": 1e-3, + "Nanoseconds (ns)": 1e-6, +} + + +class SurveyOptions(BaseModel): + model_config = ConfigDict(arbitrary_types_allowed=True) + + entity: AirborneTEMReceivers + + @property + def waveform(self) -> np.ndarray: + """Waveform in expected units of milliseconds.""" + waveform = self.entity.waveform.copy() + waveform[:, 0] *= TIME_UNIT_CONVERSION[self.entity.unit] + return waveform + + @property + def timing_mark(self) -> float: + """Timing mark in expected units of milliseconds.""" + timing_mark = self.entity.timing_mark + timing_mark *= TIME_UNIT_CONVERSION[self.entity.unit] + return timing_mark + + @property + def channels(self) -> np.ndarray: + """Channels in expected units of milliseconds.""" + channels = self.entity.channels.copy() + channels *= TIME_UNIT_CONVERSION[self.entity.unit] + return channels + + @property + def frequency(self) -> float: + """ + Transmitter frequency. + + Can be set by the user in the metadata. If not set, it will be assumed + that the waveform property contains a full halfcycle and the frequency + will be calculated as the reciprocal of the provided waveform time span. + """ + frequency = self.entity.metadata.get("frequency", None) + if frequency is None: + half_cycle_seconds = (self.waveform[-1, 0] - self.waveform[0, 0]) / 1e3 + frequency = 1 / (2 * half_cycle_seconds) + + return frequency + + @property + def channel_widths(self) -> np.ndarray: + """Time channel widths.""" + channel_widths = self.entity.metadata.get("channel_widths", None) + if channel_widths is None: + channel_widths = np.diff(np.r_[0, self.channels]) + else: + channel_widths *= TIME_UNIT_CONVERSION[self.entity.unit] + return channel_widths + + @property + def _ontime(self) -> float: + """Time at which the transmitter current turns off.""" + return float(self.waveform[self._offtime_mask(), 0][0]) + + @property + def offtime(self) -> float: + """ + Time at which the transmitter current is zero. + + This offtime is based on system frequency and may include times that + are not accounted for by the waveform. + """ + half_cycle = 1000 / (2 * self.frequency) + zero_current_ind = np.where(self.waveform[:, 1] == 0)[0] + first_zero = 1 if self.waveform[0, 1] == 0.0 else 0 + ontime = self.waveform[zero_current_ind][first_zero, 0] + + return half_cycle - ontime + + @property + def ontime_waveform(self) -> np.ndarray: + """On-time waveform including leading and trailing 0 current times.""" + ontime_waveform = self.waveform[~self._offtime_mask(), :] + endpoint = self.waveform[self._offtime_mask()][0, :] + return np.vstack([ontime_waveform, endpoint]) + + @property + def n_stations(self) -> int: + """Number of survey stations at which time channel data will be simulated.""" + return len(self.entity.locations) + + def drape_height(self, topo: np.ndarray) -> np.ndarray: + """ + Survey height over topography. + + :param topo: Topography array of x, y, elevation. + + :returns Array of survey height over topography. + """ + + survey_locs = self.entity.locations.copy() + topo_interp = LinearNDInterpolator(topo[:, :2], topo[:, 2]) + topo_at_survey_locations = topo_interp(survey_locs[:, 0], survey_locs[:, 1]) + + return survey_locs[:, 2] - topo_at_survey_locations + + def _offtime_mask(self) -> np.ndarray: + """Mask selecting off-time rows from the waveform array.""" + mask = np.isclose(self.waveform[:, 1], 0) + mask[0] = False + return mask + + +class LeroiAirOptions(BaseModel): + """Configuration for a LeroiAir airborne TEM forward simulation.""" + + model_config = ConfigDict(arbitrary_types_allowed=True) + + title: str = "LeroiAir modelling for plate-simulation package." + out_group: SimPEGGroup + survey: SurveyOptions + topo: np.ndarray + layer_resistivities: list[float] + layer_thicknesses: list[float] + plate_resistivities: list[float] + plate_geometries: list[PlateModel] + cell_size: float = 10.0 + step: bool = True + domain: Literal["time", "frequency"] = "time" + layered_earth_only: bool = False + float_precision: int = 4 + + @model_validator(mode="after") + def validate_layer_lengths_match(self) -> Self: + """Ensure layer resistivities and thicknesses have equal length.""" + if len(self.layer_resistivities) != len(self.layer_thicknesses): + raise ValueError( + "layer_resistivities and layer_thicknesses must have the same length." + ) + return self + + @model_validator(mode="after") + def validate_plate_lengths_match(self) -> Self: + """Ensure plate resistivities and geometries have equal length.""" + if len(self.plate_resistivities) != len(self.plate_geometries): + raise ValueError( + "plate_resistivities and plate_geometries must have the same length." + ) + return self + + @classmethod + def from_plate_simulation_options( + cls, model: ModelOptions, simulation: TDEMForwardOptions + ) -> Self: + """Construct from a :class:`PlateSimulationOptions` instance.""" + + survey = simulation.data_object + + if simulation.active_cells.topography_object is None: + raise NotImplementedError( + "Passing active cells directly does not currently work for simulating " + "plates using the LeroiAir option. Simulation options must contain a " + "topography object." + ) + + topo_xyz = InversionTopography(survey.workspace, simulation).locations + + return cls( + survey=SurveyOptions(entity=survey), + topo=topo_xyz, + layer_resistivities=[ + model.overburden_options.overburden_property, + model.background, + ], + layer_thicknesses=[model.overburden_options.thickness, 9999], + plate_resistivities=[model.plate_options.plate_property], + plate_geometries=[model.plate_options.geometry], + step="dBdt" in getattr(simulation, "data_units", "dBdt"), + out_group=simulation.out_group, + ) + + @property + def n_layers(self) -> int: + """Number of background layers.""" + return len(self.layer_resistivities) + + @property + def n_plates(self) -> int: + """Number of plates.""" + return len(self.plate_geometries) + + @property + def resistivities(self) -> np.ndarray: + """All resistivities.""" + return np.hstack([self.layer_resistivities, self.plate_resistivities]) + + @property + def conductivity_thicknesses(self) -> np.ndarray: + """All conductivity thicknesses.""" + sigma = [] + layer_conductivities = 1 / np.array(self.layer_resistivities) + sigma.append(self.layer_thicknesses * layer_conductivities) + plate_conductivities = 1 / np.array(self.plate_resistivities) + sigma.append([g.width for g in self.plate_geometries] * plate_conductivities) + + return np.hstack(sigma) diff --git a/simpeg_drivers/plate_simulation/match/driver.py b/simpeg_drivers/plate_simulation/match/driver.py index 65f4cd3dd..447089f02 100644 --- a/simpeg_drivers/plate_simulation/match/driver.py +++ b/simpeg_drivers/plate_simulation/match/driver.py @@ -165,7 +165,6 @@ def start(cls, filepath: str | Path, mode="r+", **_) -> Self: logger.info("Loading input file . . .") filepath = Path(filepath).resolve() - # TODO: Replace with UIJson when fully implemented uijson = SimPEGDriversUIJson.read(filepath) with Workspace(uijson.geoh5, mode=mode) as workspace: diff --git a/simpeg_drivers/plate_simulation/models/series.py b/simpeg_drivers/plate_simulation/models/series.py index 4ae0ddb01..57020d1d8 100644 --- a/simpeg_drivers/plate_simulation/models/series.py +++ b/simpeg_drivers/plate_simulation/models/series.py @@ -78,8 +78,6 @@ class Lithology(Series): 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]) diff --git a/simpeg_drivers/plate_simulation/options.py b/simpeg_drivers/plate_simulation/options.py index 28d47ec00..f6a18e516 100644 --- a/simpeg_drivers/plate_simulation/options.py +++ b/simpeg_drivers/plate_simulation/options.py @@ -15,6 +15,7 @@ from geoapps_utils.base import Options from geoh5py.groups import SimPEGGroup, UIJsonGroup from geoh5py.shared.utils import fetch_active_workspace +from pydantic import model_validator from simpeg_drivers import assets_path from simpeg_drivers.options import BaseForwardOptions @@ -49,6 +50,16 @@ class PlateSimulationOptions(Options): mesh: MeshOptions model: ModelOptions simulation: SimPEGGroup | UIJsonGroup + use_leroi: bool = False + + @model_validator(mode="before") + @classmethod + def use_leroi_em_only(cls, data) -> dict: + run_command = data["simulation"].options["run_command"] + is_tem = "time_domain.forward" in run_command + use_leroi = data.get("use_leroi", False) and is_tem + data["use_leroi"] = use_leroi + return data def simulation_parameters(self) -> BaseForwardOptions: """ diff --git a/simpeg_drivers/plate_simulation/sweep/driver.py b/simpeg_drivers/plate_simulation/sweep/driver.py index 10731872d..b040d1f37 100644 --- a/simpeg_drivers/plate_simulation/sweep/driver.py +++ b/simpeg_drivers/plate_simulation/sweep/driver.py @@ -42,7 +42,6 @@ logger = get_logger(name=__name__, level_name=False, propagate=False, add_name=False) -# TODO: Can we make this generic (PlateSweepDriver -> SweepDriver)? class PlateSweepDriver(Driver): """Sets up and manages workers to run all combinations of swepts parameters.""" diff --git a/simpeg_drivers/plate_simulation/sweep/options.py b/simpeg_drivers/plate_simulation/sweep/options.py index 8165051ee..0789b8b00 100644 --- a/simpeg_drivers/plate_simulation/sweep/options.py +++ b/simpeg_drivers/plate_simulation/sweep/options.py @@ -162,7 +162,6 @@ def all_hashable_options(options: dict, workspace: Workspace) -> dict: "icon", "documentation", ] - # TODO: add these to the Options fields with empty string defaults. out = {} for k, v in data.items(): if k in exceptions: diff --git a/simpeg_drivers/potential_fields/gravity/forward.py b/simpeg_drivers/potential_fields/gravity/forward.py index 31e3ece33..8ac83c7af 100644 --- a/simpeg_drivers/potential_fields/gravity/forward.py +++ b/simpeg_drivers/potential_fields/gravity/forward.py @@ -11,11 +11,9 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import ForwardDriver from simpeg_drivers.potential_fields.gravity.options import GravityForwardOptions +from simpeg_drivers.utils.utils import argument_parser class GravityForwardDriver(ForwardDriver): @@ -25,5 +23,5 @@ class GravityForwardDriver(ForwardDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - GravityForwardDriver.start_dask_run(file) + file, args = argument_parser() + GravityForwardDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/potential_fields/gravity/inversion.py b/simpeg_drivers/potential_fields/gravity/inversion.py index e3ab12273..1b9b1eec0 100644 --- a/simpeg_drivers/potential_fields/gravity/inversion.py +++ b/simpeg_drivers/potential_fields/gravity/inversion.py @@ -11,11 +11,9 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import InversionDriver from simpeg_drivers.potential_fields.gravity.options import GravityInversionOptions +from simpeg_drivers.utils.utils import argument_parser class GravityInversionDriver(InversionDriver): @@ -25,5 +23,5 @@ class GravityInversionDriver(InversionDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - GravityInversionDriver.start_dask_run(file) + file, args = argument_parser() + GravityInversionDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/potential_fields/magnetic_scalar/forward.py b/simpeg_drivers/potential_fields/magnetic_scalar/forward.py index a4ae22af6..8b4e18b87 100644 --- a/simpeg_drivers/potential_fields/magnetic_scalar/forward.py +++ b/simpeg_drivers/potential_fields/magnetic_scalar/forward.py @@ -11,13 +11,11 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import ForwardDriver from simpeg_drivers.potential_fields.magnetic_scalar.options import ( MagneticForwardOptions, ) +from simpeg_drivers.utils.utils import argument_parser class MagneticForwardDriver(ForwardDriver): @@ -27,5 +25,5 @@ class MagneticForwardDriver(ForwardDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - MagneticForwardDriver.start_dask_run(file) + file, args = argument_parser() + MagneticForwardDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/potential_fields/magnetic_scalar/inversion.py b/simpeg_drivers/potential_fields/magnetic_scalar/inversion.py index 02fdab992..8ff3df862 100644 --- a/simpeg_drivers/potential_fields/magnetic_scalar/inversion.py +++ b/simpeg_drivers/potential_fields/magnetic_scalar/inversion.py @@ -11,13 +11,11 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import InversionDriver from simpeg_drivers.potential_fields.magnetic_scalar.options import ( MagneticInversionOptions, ) +from simpeg_drivers.utils.utils import argument_parser class MagneticInversionDriver(InversionDriver): @@ -27,5 +25,5 @@ class MagneticInversionDriver(InversionDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - MagneticInversionDriver.start_dask_run(file) + file, args = argument_parser() + MagneticInversionDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/potential_fields/magnetic_vector/forward.py b/simpeg_drivers/potential_fields/magnetic_vector/forward.py index e7eeaeedd..ac009beda 100644 --- a/simpeg_drivers/potential_fields/magnetic_vector/forward.py +++ b/simpeg_drivers/potential_fields/magnetic_vector/forward.py @@ -11,13 +11,11 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import ForwardDriver from simpeg_drivers.potential_fields.magnetic_vector.options import ( MagneticVectorForwardOptions, ) +from simpeg_drivers.utils.utils import argument_parser class MagneticVectorForwardDriver(ForwardDriver): @@ -27,5 +25,5 @@ class MagneticVectorForwardDriver(ForwardDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - MagneticVectorForwardDriver.start_dask_run(file) + file, args = argument_parser() + MagneticVectorForwardDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/potential_fields/magnetic_vector/inversion.py b/simpeg_drivers/potential_fields/magnetic_vector/inversion.py index c94bc783c..0337aab85 100644 --- a/simpeg_drivers/potential_fields/magnetic_vector/inversion.py +++ b/simpeg_drivers/potential_fields/magnetic_vector/inversion.py @@ -11,13 +11,14 @@ from __future__ import annotations -import sys -from pathlib import Path +from geoapps_utils.utils.importing import GeoAppsError +from geoh5py.objects import DrapeModel, Octree -from simpeg_drivers.driver import InversionDriver +from simpeg_drivers.driver import InversionDriver, first_child_of_type from simpeg_drivers.potential_fields.magnetic_vector.options import ( MagneticVectorInversionOptions, ) +from simpeg_drivers.utils.utils import argument_parser class MagneticVectorInversionDriver(InversionDriver): @@ -25,7 +26,36 @@ class MagneticVectorInversionDriver(InversionDriver): _params_class = MagneticVectorInversionOptions + def _reset_models(self, iteration: int): + """ + Reset the inversion models based on specified iteration and mesh. + + :param iteration: The iteration number to reset the models for. + :param mesh: The mesh to reset the models from. + """ + mesh = first_child_of_type(self.out_group, (DrapeModel, Octree)) + flag = f"Iteration_{iteration}_" + count = 0 + for child in mesh.children: + if flag not in child.name: + continue + + if "amplitude" in child.name: + self.params.models.starting_model = child + else: + name = "starting_" + child.name.split("_")[2] + setattr(self.params.models, name, child) + count += 1 + + if count == 3: + return + + if count != 3: + raise GeoAppsError( + f"Could not reset the inversion at iteration {iteration}, no models found." + ) + if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - MagneticVectorInversionDriver.start_dask_run(file) + file, args = argument_parser() + MagneticVectorInversionDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/potential_fields/magnetic_vector_pde/forward.py b/simpeg_drivers/potential_fields/magnetic_vector_pde/forward.py index 3257af320..19c6b7c0b 100644 --- a/simpeg_drivers/potential_fields/magnetic_vector_pde/forward.py +++ b/simpeg_drivers/potential_fields/magnetic_vector_pde/forward.py @@ -11,13 +11,11 @@ from __future__ import annotations -import sys -from pathlib import Path - from simpeg_drivers.driver import ForwardDriver from simpeg_drivers.potential_fields.magnetic_vector_pde.options import ( MagneticVectorPDEForwardOptions, ) +from simpeg_drivers.utils.utils import argument_parser class MagneticVectorPDEForwardDriver(ForwardDriver): @@ -27,5 +25,5 @@ class MagneticVectorPDEForwardDriver(ForwardDriver): if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - MagneticVectorPDEForwardDriver.start_dask_run(file) + file, args = argument_parser() + MagneticVectorPDEForwardDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/potential_fields/magnetic_vector_pde/inversion.py b/simpeg_drivers/potential_fields/magnetic_vector_pde/inversion.py index 63cc4eab7..18a2c6755 100644 --- a/simpeg_drivers/potential_fields/magnetic_vector_pde/inversion.py +++ b/simpeg_drivers/potential_fields/magnetic_vector_pde/inversion.py @@ -11,18 +11,18 @@ from __future__ import annotations -import sys -from pathlib import Path - import numpy as np +from geoapps_utils.utils.importing import GeoAppsError +from geoh5py.objects import DrapeModel, Octree from simpeg.maps import Projection from simpeg.objective_function import ComboObjectiveFunction from simpeg.regularization import BaseRegularization, VectorAmplitude -from simpeg_drivers.driver import InversionDriver +from simpeg_drivers.driver import InversionDriver, first_child_of_type from simpeg_drivers.potential_fields.magnetic_vector_pde.options import ( MagneticVectorPDEInversionOptions, ) +from simpeg_drivers.utils.utils import argument_parser class MagneticVectorPDEInversionDriver(InversionDriver): @@ -54,7 +54,35 @@ def get_regularization(self): return ComboObjectiveFunction(objfcts=reg_funcs) + def _reset_models(self, iteration: int): + """ + Reset the inversion models based on specified iteration. + + :param iteration: The iteration number to reset the models for. + """ + mesh = first_child_of_type(self.out_group, (DrapeModel, Octree)) + flag = f"Iteration_{iteration}_" + count = 0 + for child in mesh.children: + if flag not in child.name: + continue + + if "amplitude" in child.name: + self.params.models.starting_model = child + else: + name = "starting_" + child.name.split("_")[2] + setattr(self.params.models, name, child) + count += 1 + + if count == 3: + return + + if count != 3: + raise GeoAppsError( + f"Could not reset the inversion at iteration {iteration}, no models found." + ) + if __name__ == "__main__": - file = Path(sys.argv[1]).resolve() - MagneticVectorPDEInversionDriver.start_dask_run(file) + file, args = argument_parser() + MagneticVectorPDEInversionDriver.start_dask_run(file, **args) diff --git a/simpeg_drivers/utils/nested.py b/simpeg_drivers/utils/nested.py index 913f4981b..2a6c5dc35 100644 --- a/simpeg_drivers/utils/nested.py +++ b/simpeg_drivers/utils/nested.py @@ -545,7 +545,6 @@ def tile_locations( grid_locs = grid_locs[sorting, :] # Cluster - # TODO turn off filter once sklearn has dealt with the issue causing the warning with warnings.catch_warnings(): warnings.simplefilter("ignore", category=UserWarning) from sklearn.cluster import KMeans diff --git a/simpeg_drivers/utils/regularization.py b/simpeg_drivers/utils/regularization.py index a109c7f6e..0b9fe38f0 100644 --- a/simpeg_drivers/utils/regularization.py +++ b/simpeg_drivers/utils/regularization.py @@ -48,17 +48,15 @@ def cell_neighbors_along_axis(mesh: TreeMesh, axis: str) -> np.ndarray: def clean_index_array(index_array: np.ndarray) -> np.ndarray: """ - Remove duplicate rows or rows with -1 in index array. + Remove rows with -1 in index array. :param index_array: Array of index pairs. :return: Cleaned array of index pairs. """ - array = np.unique(index_array, axis=0) - # Remove all the -1 for TreeMesh - mask = ~np.any(array == -1, axis=1) - return array[mask, :] + mask = ~np.any(index_array == -1, axis=1) + return index_array[mask, :] def collect_all_neighbors( @@ -183,10 +181,10 @@ def cell_neighbors(mesh: TreeMesh) -> np.ndarray: :param mesh: Input TreeMesh. - :return: Array of unique and sorted cell neighbor pairs. + :return: Array of cell neighbor index pairs (cell_id, neighbor_cell_id). """ neighbors_lists = cell_neighbors_lists(mesh) - return np.unique(np.vstack(neighbors_lists), axis=1) + return np.vstack(neighbors_lists) def rotate_xz_2d(mesh: TreeMesh, phi: np.ndarray) -> ssp.csr_matrix: diff --git a/simpeg_drivers/utils/tile_estimate.py b/simpeg_drivers/utils/tile_estimate.py index 53f8bb807..ce564d26b 100644 --- a/simpeg_drivers/utils/tile_estimate.py +++ b/simpeg_drivers/utils/tile_estimate.py @@ -108,7 +108,7 @@ def get_results(self, max_tiles: int = 13) -> dict: padding_cells=self.driver.params.padding_cells, ) - # Estimate total size in Gb + # Estimate total size in GB results[count] = float(sim.survey.nD) * mapping.shape[0] * count * 8 * 1e-9 return results @@ -117,14 +117,12 @@ def run(self) -> SimPEGGroup: """ Run the tile estimator. """ - # TODO find out why this is needed. Without I get an error because the - # data_object is no longer the parent of tmi_channel. _ = self.driver.inversion # Triggers creation of something results = self.get_results() logger.info( "Estimates:\n%s\n%s", - "Tiling \t Total size (Gb) ", + "Tiling \t Total size (GB) ", "\n".join(f"{key} \t {value:.2e}" for key, value in results.items()), ) @@ -249,7 +247,7 @@ def plot(results: dict, data: InversionData, optimal: int): ax.plot(optimal, fun(optimal), "ro") ax.set_xlabel("Number of tiles") ax.set_aspect(tile_counts.max() / problem_sizes.max()) - ax.set_ylabel("Est. total size (Gb)") + ax.set_ylabel("Est. total size (GB)") ax2 = plt.subplot(2, 1, 2) tiles = tile_locations( diff --git a/simpeg_drivers/utils/utils.py b/simpeg_drivers/utils/utils.py index 7100f55e6..48c762846 100644 --- a/simpeg_drivers/utils/utils.py +++ b/simpeg_drivers/utils/utils.py @@ -16,6 +16,7 @@ import multiprocessing import pstats import sys +from argparse import ArgumentParser from copy import deepcopy from pathlib import Path from typing import TYPE_CHECKING @@ -50,6 +51,7 @@ if TYPE_CHECKING: from simpeg_drivers.components.data import InversionData + from simpeg_drivers.driver import InversionDriver def mask_vertices_and_cells( @@ -705,19 +707,24 @@ def start_dask_run( json_path: Path, n_workers: int | None = None, n_threads: int | None = None, + generate_report: bool = False, + start_iteration: int = -1, ): """ Runs an application with Dask optimization. + :param class_type: Class type to run the application with. :param json_path: Path to input file (.ui.json) for the application. :param n_workers: Number of workers to use. :param n_threads: Number of threads to use. + :param generate_report: Flag to indicate whether to generate a performance report. + :param start_iteration: Iteration to warm-start the inversion if possible. """ ui_json = load_ui_json_as_dict(json_path) n_workers = ui_json.get("n_workers", n_workers) n_threads = ui_json.get("n_threads", n_threads) - save_report = ui_json.get("performance_report", False) + save_report = ui_json.get("performance_report", generate_report) if (n_workers is not None and n_workers > 1) or n_threads is not None: cluster = LocalCluster( @@ -742,7 +749,7 @@ def start_dask_run( if (save_report and isinstance(context_client, Client)) else contextlib.nullcontext() ): - class_type.start(json_path) + class_type.start(json_path, start_iteration=start_iteration) sys.stdout.close() profiler.disable() @@ -760,8 +767,6 @@ def driver_class_from_name(name: str, forward_only: bool = False) -> type[Driver """ Get the driver class from the inversion type name. - TODO: Only for backward compatibility. To be deprecated in future versions. - :param name: The inversion type name. :param forward_only: Whether to forward only the forward operators. @@ -815,3 +820,20 @@ def driver_class_from_dict(data: dict) -> type[Driver]: "Could not find a driver for the given 'inversion_type' or 'run_command' parameter. " "Please review the 'run_command' in the UI JSON." ) + + +def argument_parser() -> tuple: + parser = ArgumentParser( + prog="simpeg_drivers.driver", + description="Launch a forward or inversion driver for simpeg.", + ) + + parser.add_argument("file", type=Path) + parser.add_argument("-i", "--start_iteration", type=int, required=False, default=-1) + parser.add_argument("-w", "--n_workers", type=int, required=False, default=None) + parser.add_argument("-t", "--n_threads", type=int, required=False, default=None) + parser.add_argument("-r", "--generate_report", action="store_true") + + arguments = vars(parser.parse_args()) + + return arguments.pop("file"), arguments diff --git a/tests/plate_simulation/leroi_air/__init__.py b/tests/plate_simulation/leroi_air/__init__.py new file mode 100644 index 000000000..94728ad80 --- /dev/null +++ b/tests/plate_simulation/leroi_air/__init__.py @@ -0,0 +1,56 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2026 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.modelling.plates import PlateModel +from geoh5py.groups import SimPEGGroup +from geoh5py.shared.utils import fetch_active_workspace + +from simpeg_drivers.plate_simulation.leroi_air.options import ( + LeroiAirOptions, + SurveyOptions, +) +from simpeg_drivers.utils.synthetics.surveys.time_domain.airborne_tdem import ( + generate_airborne_tdem_survey, +) + + +def generate_plate_options(workspace): + x = np.linspace(-1000, 1000, 81) + y = np.linspace(-1000, 1000, 81) + X, Y = np.meshgrid(x, y) + Z = np.full_like(X, 20.0) + + with fetch_active_workspace(workspace) as geoh5: + out_group = SimPEGGroup.create(geoh5) + survey = generate_airborne_tdem_survey(geoh5, X=X, Y=Y, Z=Z) + layer_resistivities = [1500.0, 2000.0, 5000.0] + layer_thicknesses = [50.0, 1000.0, 2000.0] + plate_resistivities = [100.0] + plate_geometries = [ + PlateModel( + strike_length=200, + dip_length=100, + width=10, + direction=90, + dip=45, + ) + ] + topo = np.column_stack([X.flatten(), Y.flatten(), np.zeros(X.size)]) + opts = LeroiAirOptions( + survey=SurveyOptions(entity=survey), + topo=topo, + layer_resistivities=layer_resistivities, + layer_thicknesses=layer_thicknesses, + plate_resistivities=plate_resistivities, + plate_geometries=plate_geometries, + out_group=out_group, + ) + return opts diff --git a/tests/plate_simulation/leroi_air/conftest.py b/tests/plate_simulation/leroi_air/conftest.py new file mode 100644 index 000000000..8ed2318fa --- /dev/null +++ b/tests/plate_simulation/leroi_air/conftest.py @@ -0,0 +1,86 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2026 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 unittest.mock import MagicMock + +import pytest +from geoh5py import Workspace + +from simpeg_drivers.plate_simulation.leroi_air.interface import ( + LeroiAirInput, + LeroiAirOutput, +) + +from . import generate_plate_options + + +N_CHANNELS = 3 +N_STATIONS = 2 + +FAKE_OUT = """\ + VERTICAL COMPONENT - nT/s + + TRANSMITTER POSITION CHNL 1 CHNL 2 CHNL 3 + EAST NORTH ALT 2.300 2.600 3.200 + + 1 -100 0 13 13.3 14.4 15.5 + 2 -87 0 13 16.6 17.7 18.8 +------------------------------------------------------------------------------------- + + + IN-LINE COMPONENT - nT/s + + TRANSMITTER POSITION CHNL 1 CHNL 2 CHNL 3 + EAST NORTH ALT 2.300 2.600 3.200 + + 1 -100 0 13 7.7 8.8 9.9 + 2 -87 0 13 10.0 11.1 12.2 +------------------------------------------------------------------------------------- + + + TRANSVERSE COMPONENT - nT/s + + TRANSMITTER POSITION CHNL 1 CHNL 2 CHNL 3 + EAST NORTH ALT 2.300 2.600 3.200 + + 1 -100 0 13 1.1 2.2 3.3 + 2 -87 0 13 4.4 5.5 6.6 +------------------------------------------------------------------------------------- +""" + + +@pytest.fixture +def mock_input(): + opts = MagicMock() + opts.float_precision = 4 + return LeroiAirInput(opts) + + +@pytest.fixture +def mock_output(): + opts = MagicMock() + opts.n_stations = N_STATIONS + opts.float_precision = 4 + return LeroiAirOutput(opts=opts) + + +@pytest.fixture(scope="module") +def plate_options(tmp_path_factory): + tmp_path = tmp_path_factory.mktemp("leroi_air_options") + with Workspace(tmp_path / "test.geoh5") as geoh5: + opts = generate_plate_options(workspace=geoh5) + return opts + + +@pytest.fixture +def fake_outfile(tmp_path): + path = tmp_path / "leroi.out" + path.write_text(FAKE_OUT, encoding="utf-8") + return path diff --git a/tests/plate_simulation/leroi_air/driver_test.py b/tests/plate_simulation/leroi_air/driver_test.py new file mode 100644 index 000000000..106b4b920 --- /dev/null +++ b/tests/plate_simulation/leroi_air/driver_test.py @@ -0,0 +1,44 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2026 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 subprocess +from unittest.mock import MagicMock, patch + +import pytest +from geoh5py import Workspace + +from simpeg_drivers.plate_simulation.leroi_air.driver import LeroiAirDriver + +from . import generate_plate_options + + +# pylint: disable=protected-access + + +def test_leroi_air_driver_run_raises_on_subprocess_failure(tmp_path): + with Workspace(tmp_path / "test.geoh5") as geoh5: + opts = generate_plate_options(workspace=geoh5) + + driver = LeroiAirDriver(opts) + driver._interface = MagicMock() + failed_result = MagicMock( + spec=subprocess.CompletedProcess, returncode=1, stderr="err", stdout="out" + ) + + with patch( + "simpeg_drivers.plate_simulation.leroi_air.driver.subprocess.run", + return_value=failed_result, + ): + with pytest.raises(RuntimeError) as exc_info: + driver.run() + + assert "return code 1" in str(exc_info.value) + assert "err" in str(exc_info.value) + assert "out" in str(exc_info.value) diff --git a/tests/plate_simulation/leroi_air/interface_test.py b/tests/plate_simulation/leroi_air/interface_test.py new file mode 100644 index 000000000..05ec98018 --- /dev/null +++ b/tests/plate_simulation/leroi_air/interface_test.py @@ -0,0 +1,158 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2026 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 simpeg_drivers.plate_simulation.leroi_air.interface import ( + LeroiAirInput, + LeroiAirOutput, +) + +from . import generate_plate_options +from .conftest import FAKE_OUT, N_CHANNELS, N_STATIONS + + +# pylint: disable=protected-access + + +def test_line_formatting(tmp_path): + with Workspace(tmp_path / "test.geoh5") as geoh5: + opts = generate_plate_options(geoh5) + + leroi_input = LeroiAirInput(opts=opts) + + line = leroi_input.format_line(["TXCLN", "CMP", "KPPM"]) + assert line == "0.0 3 0 \t ! TXCLN, CMP, KPPM" + + line = leroi_input.format_line("TMS") + assert line == "2.3 2.6 3.2 \t ! TMS" + + multi_line = leroi_input.format_multi_line(["TMS", "WIDTH"]) + assert multi_line == "2.3 0.3\n2.6 0.3\n3.2 0.6\t ! TMS, WIDTH" + + +def test_format_cfl_file(tmp_path): + with Workspace(tmp_path / "test.geoh5") as geoh5: + opts = generate_plate_options(geoh5) + + leroi_input = LeroiAirInput(opts=opts) + cfl_str = leroi_input.format_cfl_file() + + # title + assert opts.title in cfl_str + + # record_2: TDFD, DO3D, PRFL, ISTOP + assert "1 1 1 0 \t ! TDFD, DO3D, PRFL, ISTOP" in cfl_str + + # record_3: ISW, NSX, STEP, UNITS, NCHNL, KRXW, OFFTIME + assert "1 8 1 1 3 2 1.6 \t ! ISW, NSX, STEP, UNITS, NCHNL, KRXW, OFFTIME" in cfl_str + + # record_4: TXON, WAVEFORM (multi-line waveform table) + assert ( + "0.0 0.0\n" + "0.5 0.3333\n" + "1.0 0.6667\n" + "1.5 1.0\n" + "1.6 0.9\n" + "1.7 0.6000\n" + "1.8 0.3000\n" + "1.9 0.0\t ! TXON, WAVEFORM" + ) in cfl_str + + # record_5: TMS (time channel midpoints) + assert "2.3 2.6 3.2 \t ! TMS" in cfl_str + + # record_6: WIDTH (time channel widths) + assert "0.3 0.3 0.6 \t ! WIDTH" in cfl_str + + # record_7: TXCLN, CMP, KPPM + assert "0.0 3 0 \t ! TXCLN, CMP, KPPM" in cfl_str + + # record_7p1 (NPPF) is only written when KPPM > 0; default KPPM=0 so absent + assert "3 \t ! NPPF" not in cfl_str + + # record_7p2: TXAREA, NTRN + assert "1.0 1 \t ! TXAREA, NTRN" in cfl_str + + # record_8: ZRX0, XRX0, YRX0 + assert "0.0 0.0 0.0 \t ! ZRX0, XRX0, YRX0" in cfl_str + + # record_9: NSTAT, SURVEY, BAROMTRC, LINE_TAG + assert "6561 2 1 0 \t ! NSTAT, SURVEY, BAROMTRC, LINE_TAG" in cfl_str + + # record_9p1: EAST, NORTH, ALT — too long to assert explicitly + + # record_10: NLAYER, NPLATE, NLITH, GND_LVL + assert "3 1 4 0.0 \t ! NLAYER, NPLATE, NLITH, GND_LVL" in cfl_str + + # record_11: RES, SIG_T, RMU, REPS, CHRG, CTAU, CFREQ (multi-line lithology table) + assert ( + "1500.0 0.0333 1.0 1.0 0.0 0.0 1.0\n" + "2000.0 0.5 1.0 1.0 0.0 0.0 1.0\n" + "5000.0 0.4 1.0 1.0 0.0 0.0 1.0\n" + "100.0 0.1 1.0 1.0 0.0 0.0 1.0\t ! RES, SIG_T, RMU, REPS, CHRG, CTAU, CFREQ" + ) in cfl_str + + # record_12: LITH, THICK (multi-line layer table) + assert ("1 50.0\n2 1000.0\n3 2000.0\t ! LITH, THICK") in cfl_str + + # record_13: CELLW + assert "10.0 \t ! CELLW" in cfl_str + + # record_14: LITHP, CNTR_East, CNTR_North, PLTOP + assert "4 0.0 0.0 -0.0\t ! LITHP, CNTR_East, CNTR_North, PLTOP" in cfl_str + + # record_15: PLNGTH, DPWDTH, DZM, DIP + assert "200.0 100.0 90.0 45.0\t ! PLNGTH, DPWDTH, DZM, DIP" in cfl_str + + +def test_slice_data_lines_returns_correct_station_rows(mock_output): + lines = FAKE_OUT.splitlines() + anchor = LeroiAirOutput._COMPONENT_ANCHORS["crossline"] + rows = mock_output._slice_data_lines(lines, anchor) + assert len(rows) == N_STATIONS + assert rows[0].split()[4] == "1.1" + assert rows[1].split()[4] == "4.4" + + +def test_extract_data_returns_channel_columns_only(mock_output, fake_outfile): + data = mock_output._extract_data(fake_outfile, component="crossline") + assert data.shape == (N_STATIONS, N_CHANNELS) + + +def test_extract_data_parses_transverse_component_values(mock_output, fake_outfile): + data = mock_output._extract_data(fake_outfile, component="crossline") + np.testing.assert_array_almost_equal(data[0], [1.1, 2.2, 3.3]) + np.testing.assert_array_almost_equal(data[1], [4.4, 5.5, 6.6]) + + +def test_extract_data_parses_inline_component_values(mock_output, fake_outfile): + data = mock_output._extract_data(fake_outfile, component="inline") + np.testing.assert_array_almost_equal(data[0], [7.7, 8.8, 9.9]) + np.testing.assert_array_almost_equal(data[1], [10.0, 11.1, 12.2]) + + +def test_extract_data_parses_vertical_component_values(mock_output, fake_outfile): + data = mock_output._extract_data(fake_outfile, component="vertical") + np.testing.assert_array_almost_equal(data[0], [13.3, 14.4, 15.5]) + np.testing.assert_array_almost_equal(data[1], [16.6, 17.7, 18.8]) + + +def test_format_value_int(mock_input): + assert mock_input._format_value(3) == "3" + assert mock_input._format_value(np.int64(7)) == "7" + + +def test_format_value_float(mock_input): + assert mock_input._format_value(1.5) == "1.5" + assert mock_input._format_value(1.123456789) == "1.1235" + mock_input.opts.float_precision = 2 + assert mock_input._format_value(3.141592653) == "3.14" diff --git a/tests/plate_simulation/leroi_air/options_test.py b/tests/plate_simulation/leroi_air/options_test.py new file mode 100644 index 000000000..742066d09 --- /dev/null +++ b/tests/plate_simulation/leroi_air/options_test.py @@ -0,0 +1,102 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2026 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 +import pytest +from geoh5py import Workspace +from geoh5py.groups import SimPEGGroup +from pydantic import ValidationError + +from simpeg_drivers.plate_simulation.leroi_air.options import LeroiAirOptions + +from . import generate_plate_options + + +def test_options_channel_widths(plate_options): + assert np.allclose(plate_options.survey.channel_widths, [0.3, 0.3, 0.6]) + + +def test_options_channel_widths_from_metadata(tmp_path): + explicit_widths = [0.1, 0.2, 0.3] + with Workspace(tmp_path / "test.geoh5") as geoh5: + opts = generate_plate_options(workspace=geoh5) + meta = opts.survey.entity.metadata + meta["channel_widths"] = explicit_widths + opts.survey.entity.metadata = meta + + assert np.allclose(opts.survey.channel_widths, explicit_widths) + + +def test_options_frequency(plate_options): + assert np.isclose(plate_options.survey.frequency, 142.857143) + + +def test_options_frequency_from_metadata(tmp_path): + with Workspace(tmp_path / "test.geoh5") as geoh5: + opts = generate_plate_options(workspace=geoh5) + meta = opts.survey.entity.metadata + meta["frequency"] = 0.15 + opts.survey.entity.metadata = meta + + assert np.isclose(opts.survey.frequency, 0.15) + + +def test_options_offtime(plate_options): + assert np.isclose(plate_options.survey.offtime, 1.6) + + +def test_options_ontime_waveform(plate_options): + assert np.allclose( + plate_options.survey.ontime_waveform, + plate_options.survey.entity.waveform[0:8, :], + ) + + +def test_options_conductivity_thicknesses(plate_options): + assert np.allclose( + plate_options.conductivity_thicknesses, np.array([0.0333333, 0.5, 0.4, 0.1]) + ) + + +def test_options_resistivities(plate_options): + assert np.allclose(plate_options.resistivities, [1500.0, 2000.0, 5000.0, 100.0]) + + +def test_options_drape_height(plate_options): + assert np.allclose(plate_options.survey.drape_height(plate_options.topo), 20.0) + + +def test_options_validate_layer_lengths_mismatch(plate_options): + ws = Workspace() + out_group = SimPEGGroup.create(ws) + with pytest.raises(ValidationError, match="layer_resistivities"): + LeroiAirOptions( + survey=plate_options.survey, + topo=plate_options.topo, + layer_resistivities=[1500.0], + layer_thicknesses=[50.0, 1000.0], + plate_resistivities=plate_options.plate_resistivities, + plate_geometries=plate_options.plate_geometries, + out_group=out_group, + ) + + +def test_options_validate_plate_lengths_mismatch(plate_options): + ws = Workspace() + out_group = SimPEGGroup.create(ws) + with pytest.raises(ValidationError, match="plate_resistivities"): + LeroiAirOptions( + survey=plate_options.survey, + topo=plate_options.topo, + layer_resistivities=plate_options.layer_resistivities, + layer_thicknesses=plate_options.layer_thicknesses, + plate_resistivities=[100.0, 200.0], + plate_geometries=plate_options.plate_geometries, + out_group=out_group, + ) diff --git a/tests/plate_simulation/models/plates_test.py b/tests/plate_simulation/models/plates_test.py index 8ed4d3a19..74ed84d34 100644 --- a/tests/plate_simulation/models/plates_test.py +++ b/tests/plate_simulation/models/plates_test.py @@ -58,18 +58,9 @@ def test_vertical_east_striking_plate(tmp_path): vertical_east_striking.extent[1, 2] - vertical_east_striking.extent[0, 2], 500.0, ) - assert np.isclose( - vertical_east_striking.vertices[:, 0].mean(), - 0.0, # pylint: disable=no-member - ) - assert np.isclose( - vertical_east_striking.vertices[:, 1].mean(), - 0.0, # pylint: disable=no-member - ) - assert np.isclose( - vertical_east_striking.vertices[:, 2].mean(), - -250.0, # pylint: disable=no-member - ) + assert np.isclose(vertical_east_striking.vertices[:, 0].mean(), 0.0) + assert np.isclose(vertical_east_striking.vertices[:, 1].mean(), 0.0) + assert np.isclose(vertical_east_striking.vertices[:, 2].mean(), -250.0) def test_dipping_plates_all_quadrants(tmp_path): @@ -102,13 +93,13 @@ def test_dipping_plates_all_quadrants(tmp_path): def test_replicate_even(tmp_path): with Workspace(tmp_path / "test.geoh5") as workspace: options = PlateModel( - strike_length=2.0, - dip_length=2.0, - width=2.0, + strike_length=1.0, + dip_length=1.0, + width=1.0, direction=0.0, dip=0.0, easting=0.0, - northing=-1.0, + northing=0.0, elevation=0.0, ) plate = Plate(options) @@ -117,24 +108,24 @@ def test_replicate_even(tmp_path): assert plates[1].surface(workspace).vertices is not None assert np.allclose( plates[0].surface(workspace).vertices.mean(axis=0), - np.array([-5.0, 0.0, 0.0]), + np.array([-5.0, 0.5, 0.0]), ) assert np.allclose( plates[1].surface(workspace).vertices.mean(axis=0), - np.array([5.0, 0.0, 0.0]), + np.array([5.0, 0.5, 0.0]), ) def test_replicate_odd(tmp_path): with Workspace(tmp_path / "test.geoh5") as workspace: options = PlateModel( - strike_length=2.0, - dip_length=2.0, - width=2.0, + strike_length=1.0, + dip_length=1.0, + width=1.0, direction=0.0, dip=0.0, easting=0.0, - northing=-1.0, + northing=0.0, elevation=0.0, ) plate = Plate(options) @@ -144,13 +135,13 @@ def test_replicate_odd(tmp_path): assert plates[2].surface(workspace).vertices is not None assert np.allclose( plates[0].surface(workspace).vertices.mean(axis=0), - np.array([0.0, -5.0, 0.0]), + np.array([0.0, -4.5, 0.0]), ) assert np.allclose( plates[1].surface(workspace).vertices.mean(axis=0), - np.array([0.0, 0.0, 0.0]), + np.array([0.0, 0.5, 0.0]), ) assert np.allclose( plates[2].surface(workspace).vertices.mean(axis=0), - np.array([0.0, 5.0, 0.0]), + np.array([0.0, 5.5, 0.0]), ) diff --git a/tests/plate_simulation/runtest/conftest.py b/tests/plate_simulation/runtest/conftest.py new file mode 100644 index 000000000..dddedfa31 --- /dev/null +++ b/tests/plate_simulation/runtest/conftest.py @@ -0,0 +1,33 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2026 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 os +import sys +from pathlib import Path + +import pytest + + +@pytest.fixture(autouse=True, scope="session") +def conda_scripts_on_path(): + """Ensure the conda env Scripts directory is on PATH. + PyCharm (and other IDEs) launch pytest with the configured Python interpreter + but do not fully activate the conda environment, so the interpreter's sibling + Scripts/ directory is absent from PATH. This fixture adds it once per session + so that subprocess calls to executables installed there (e.g. LeroiAir550_JR) + resolve correctly without hard-coding any paths in the tests themselves. + """ + scripts_dir = str(Path(sys.executable).parent / "Scripts") + original_path = os.environ.get("PATH", "") + if scripts_dir not in original_path.split(os.pathsep): + os.environ["PATH"] = scripts_dir + os.pathsep + original_path + yield + os.environ["PATH"] = original_path diff --git a/tests/plate_simulation/runtest/driver_test.py b/tests/plate_simulation/runtest/driver_test.py index 6b2f9432d..0a9ea4d65 100644 --- a/tests/plate_simulation/runtest/driver_test.py +++ b/tests/plate_simulation/runtest/driver_test.py @@ -95,17 +95,6 @@ def test_plate_simulation_params_from_input_file(tmp_path, caplog): assert "Overburden thickness exceeds the plate depth" in caplog.text 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 == geoh5.h5file - assert ( - simulation_parameters.active_cells.topography_object.uid - == components.topography.uid - ) - assert simulation_parameters.data_object.uid == components.survey.uid - assert isinstance(params.mesh, MeshOptions) assert params.mesh.u_cell_size == 10.0 assert params.mesh.v_cell_size == 10.0 diff --git a/tests/plate_simulation/runtest/gravity_test.py b/tests/plate_simulation/runtest/gravity_test.py index 4f7d618d4..25401f6ca 100644 --- a/tests/plate_simulation/runtest/gravity_test.py +++ b/tests/plate_simulation/runtest/gravity_test.py @@ -12,7 +12,6 @@ from geoapps_utils.modelling.plates import PlateModel from geoh5py.groups import SimPEGGroup -from simpeg_drivers.plate_simulation.driver import PlateSimulationDriver from simpeg_drivers.plate_simulation.models.options import ( ModelOptions, OverburdenOptions, @@ -31,71 +30,71 @@ SurveyOptions, SyntheticsComponentsOptions, ) +from tests.utils.runtests import run_driver_from_ui_json from tests.utils.targets import get_workspace def test_gravity_plate_simulation(tmp_path): - opts = SyntheticsComponentsOptions( - method="gravity", - survey=SurveyOptions(n_stations=8, n_lines=8, drape=5.0), - mesh=SyntheticsMeshOptions(), - model=SyntheticsModelOptions(anomaly=0.0), - ) - with get_workspace(tmp_path / "inversion_test.ui.geoh5") as geoh5: - components = SyntheticsComponents(geoh5, options=opts) - 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, - survey_refinement=[4, 6], - topography_refinement=[0, 1], - plate_refinement=[4, 2], - ) - overburden_params = OverburdenOptions(thickness=50.0, overburden_property=0.2) - - plate_params = PlateOptions( - name="plate", - geometry=PlateModel( - elevation=100.0, - width=100.0, - strike_length=100.0, - dip_length=100.0, - dip=0.0, - direction=0.0, - ), - plate_property=0.5, + with get_workspace(tmp_path / "inversion_test.ui.geoh5") as geoh5: + geometry = PlateModel( + elevation=100.0, + width=100.0, + strike_length=100.0, + dip_length=100.0, + dip=0.0, + direction=0.0, ) - model_params = ModelOptions( - name="density", - background=0.0, - overburden_options=overburden_params, - plate_options=plate_params, + opts = SyntheticsComponentsOptions( + method="gravity", + survey=SurveyOptions(n_stations=8, n_lines=8, drape=5.0), + mesh=SyntheticsMeshOptions(), + model=SyntheticsModelOptions(anomaly=0.0), ) + components = SyntheticsComponents(geoh5, options=opts) + out_group = SimPEGGroup.create(geoh5, name="Gravity forward") options = GravityForwardOptions.build( topography_object=components.topography, data_object=components.survey, geoh5=geoh5, starting_model=0.1, + out_group=out_group, ) - - gravity_inversion = SimPEGGroup.create(geoh5) - gravity_inversion.options = options.serialize() + options.update_out_group_options() params = PlateSimulationOptions( - title="test", - run_command="run", geoh5=geoh5, - mesh=mesh_params, - model=model_params, - simulation=gravity_inversion, + mesh=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, + survey_refinement=[4, 6], + topography_refinement=[0, 1], + plate_refinement=[4, 2], + name="mesh", + ), + model=ModelOptions( + name="density", + background=0.0, + overburden_options=OverburdenOptions( + thickness=50.0, overburden_property=0.2 + ), + plate_options=PlateOptions( + name="plate", + geometry=geometry, + plate_property=0.5, + ), + ), + simulation=options.out_group, ) - driver = PlateSimulationDriver(params) - driver.run() - assert np.nanmax(driver.model.values) == 0.5 + driver = run_driver_from_ui_json(params) + + assert ( + np.nanmax(driver.simulation_parameters.models.starting_model.values) == 0.5 + ) diff --git a/tests/plate_simulation/runtest/leroi_test.py b/tests/plate_simulation/runtest/leroi_test.py new file mode 100644 index 000000000..bff777fd8 --- /dev/null +++ b/tests/plate_simulation/runtest/leroi_test.py @@ -0,0 +1,139 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2026 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 os + +import pytest +from geoapps_utils.modelling.plates import PlateModel +from geoh5py import Workspace +from geoh5py.groups import SimPEGGroup +from geoh5py.objects import AirborneTEMReceivers, MaxwellPlate, Octree + +from simpeg_drivers.electromagnetics.time_domain.forward import ( + TDEMForwardOptions, +) +from simpeg_drivers.plate_simulation.models.options import ( + ModelOptions, + OverburdenOptions, + PlateOptions, +) +from simpeg_drivers.plate_simulation.options import MeshOptions, PlateSimulationOptions +from simpeg_drivers.utils.synthetics.driver import SyntheticsComponents +from simpeg_drivers.utils.synthetics.options import ( + MeshOptions as SyntheticsMeshOptions, +) +from simpeg_drivers.utils.synthetics.options import ( + ModelOptions as SyntheticsModelOptions, +) +from simpeg_drivers.utils.synthetics.options import ( + SurveyOptions, + SyntheticsComponentsOptions, +) +from tests.utils.runtests import run_driver_from_ui_json +from tests.utils.targets import get_workspace + + +IN_GITHUB_ACTIONS = os.getenv("GITHUB_ACTIONS") == "true" + + +@pytest.mark.skipif( + IN_GITHUB_ACTIONS, + reason="Test requires a LeroiAir550_JR.exe in the environments Scripts directory that is not stored in GitHub.", +) +def test_leroi_run(tmp_path): + + with get_workspace(tmp_path / "leroi_test.geoh5") as geoh5: + geometry = PlateModel( + easting=0.0, + northing=0.0, + elevation=100.0, + width=10.0, + strike_length=200.0, + dip_length=100.0, + dip=80.0, + direction=90.0, + ) + + opts = SyntheticsComponentsOptions( + method="airborne tdem", + survey=SurveyOptions(width=800.0, n_stations=32, n_lines=4, drape=40.0), + mesh=SyntheticsMeshOptions(), + model=SyntheticsModelOptions( + anomaly=1 / 10, + background=1 / 5000, + plate=geometry, + ), + ) + components = SyntheticsComponents(geoh5, options=opts) + out_group = SimPEGGroup.create(geoh5, name="TEM forward") + options = TDEMForwardOptions.build( + topography_object=components.topography, + data_object=components.survey, + geoh5=geoh5, + starting_model=components.model, + z_channel_bool=True, + out_group=out_group, + ) + options.update_out_group_options() + + params = PlateSimulationOptions( + geoh5=geoh5, + mesh=MeshOptions( + u_cell_size=25.0, + v_cell_size=25.0, + w_cell_size=25.0, + padding_distance=1000.0, + depth_core=600.0, + max_distance=200.0, + survey_refinement=[2, 4], + topography_refinement=[0, 1], + plate_refinement=[2, 2], + ), + model=ModelOptions( + background=2000.0, + overburden_options=OverburdenOptions( + thickness=50.0, + overburden_property=1500.0, + ), + plate_options=PlateOptions( + name="plate", + geometry=geometry, + plate_property=1.0, + ), + ), + simulation=options.out_group, + use_leroi=True, + ) + + run_driver_from_ui_json(params) + + with Workspace(tmp_path / "leroi_test.geoh5") as geoh5: + plate_simulation_group = geoh5.get_entity("Plate Simulation")[0] + forward_group = plate_simulation_group.get_entity("TEM forward")[0] + + survey = forward_group.get_entity("survey")[0] + assert isinstance(survey, AirborneTEMReceivers) + + maxwell_plate = plate_simulation_group.get_entity("Maxwell Plate")[0] + assert isinstance(maxwell_plate, MaxwellPlate) + + expected_channels = [ + "fwd inline [0]", + "fwd inline [1]", + "fwd inline [2]", + "fwd crossline [0]", + "fwd crossline [1]", + "fwd crossline [2]", + "fwd vertical [0]", + "fwd vertical [1]", + "fwd vertical [2]", + ] + for channel in expected_channels: + assert survey.get_entity(channel)[0] is not None diff --git a/tests/run_tests/driver_dc_2d_rotated_gradients_test.py b/tests/run_tests/driver_dc_2d_rotated_gradients_test.py index 90bcfc251..dd95b298f 100644 --- a/tests/run_tests/driver_dc_2d_rotated_gradients_test.py +++ b/tests/run_tests/driver_dc_2d_rotated_gradients_test.py @@ -43,11 +43,11 @@ # To test the full run and validate the inversion. # Move this file out of the test directory and run. -target_run = {"data_norm": 10.11130699043982, "phi_d": 193000, "phi_m": 439} +target_run = {"data_norm": 1.0864e01, "phi_d": 4.5530e04, "phi_m": 7.7120e02} def test_dc_rotated_2d_fwr_run( - tmp_path: Path, n_electrodes=10, n_lines=3, cell_size=(5.0, 5.0) + tmp_path: Path, n_electrodes=10, n_lines=3, cell_size=(10.0, 10.0) ): opts = SyntheticsComponentsOptions( method="direct current 2d", diff --git a/tests/run_tests/driver_dc_2d_test.py b/tests/run_tests/driver_dc_2d_test.py index 1da6ba863..3d45091e1 100644 --- a/tests/run_tests/driver_dc_2d_test.py +++ b/tests/run_tests/driver_dc_2d_test.py @@ -43,7 +43,7 @@ # To test the full run and validate the inversion. # Move this file out of the test directory and run. -target_run = {"data_norm": 10.864162198305321, "phi_d": 5370, "phi_m": 334} +target_run = {"data_norm": 1.1186e01, "phi_d": 5.1220e03, "phi_m": 3.3940e02} def test_dc_2d_fwr_run( diff --git a/tests/run_tests/driver_dc_test.py b/tests/run_tests/driver_dc_test.py index 9bce6c276..d64bb5695 100644 --- a/tests/run_tests/driver_dc_test.py +++ b/tests/run_tests/driver_dc_test.py @@ -38,7 +38,7 @@ # To test the full run and validate the inversion. # Move this file out of the test directory and run. -target_run = {"data_norm": 0.14571066077648995, "phi_d": 14.4, "phi_m": 3940} +target_run = {"data_norm": 1.3680e-01, "phi_d": 1.1720e01, "phi_m": 4.6400e03} def test_dc_3d_fwr_run( diff --git a/tests/run_tests/driver_grav_test.py b/tests/run_tests/driver_grav_test.py index 3fa65ba01..9a3a9e65b 100644 --- a/tests/run_tests/driver_grav_test.py +++ b/tests/run_tests/driver_grav_test.py @@ -10,14 +10,19 @@ from __future__ import annotations +import shutil +from io import BytesIO from pathlib import Path from unittest.mock import patch import numpy as np from geoapps_utils.utils.importing import GeoAppsError +from geoh5py.ui_json import BaseUIJson from geoh5py.workspace import Workspace +from pandas import read_csv from pytest import raises +from simpeg_drivers.driver import validate_out_group from simpeg_drivers.potential_fields.gravity.forward import ( GravityForwardDriver, GravityForwardOptions, @@ -40,17 +45,17 @@ # To test the full run and validate the inversion. # Move this file out of the test directory and run. -target_run = {"data_norm": 0.0034873276857765663, "phi_d": 3.06, "phi_m": 0.00137} +target_run = {"data_norm": 0.5770853826069609, "phi_d": 74100, "phi_m": 22.3} def test_gravity_fwr_run( tmp_path: Path, - n_grid_points=2, + n_grid_points=3, cell_size=(20.0, 20.0, 20.0), refinement=(2,), ): filepath = Path(tmp_path) / "inversion_test.ui.geoh5" - with Workspace.create(filepath) as geoh5: + with get_workspace(filepath) as geoh5: # Run the forward components = SyntheticsComponents( geoh5=geoh5, @@ -87,12 +92,15 @@ def test_gravity_fwr_run( def test_gravity_run( tmp_path: Path, - max_iterations=1, + max_iterations=2, pytest=True, ): workpath = tmp_path / "inversion_test.ui.geoh5" if pytest: - workpath = tmp_path.parent / "test_gravity_fwr_run0" / "inversion_test.ui.geoh5" + shutil.copy( + tmp_path.parent / "test_gravity_fwr_run0" / "inversion_test.ui.geoh5", + tmp_path, + ) with Workspace(workpath) as geoh5: gz = geoh5.get_entity("Iteration_0_gz")[0] @@ -130,7 +138,7 @@ def test_gravity_run( gz_uncertainty=2e-3, lower_bound=0.0, max_global_iterations=max_iterations, - initial_beta_ratio=1e-2, + initial_beta_ratio=1e-1, percentile=100, starting_model=1e-4, topography_object=components.topography, @@ -138,16 +146,26 @@ def test_gravity_run( sens_wts_threshold=1.0, save_sensitivities=True, ) + out_group = validate_out_group(params) + params.out_group = out_group params.write_ui_json(path=tmp_path / "Inv_run.ui.json") driver = GravityInversionDriver.start(str(tmp_path / "Inv_run.ui.json")) - assert driver.directives.directive_list[0].chifact_start == 0.75 - assert driver.directives.directive_list[0].chifact_target == 0.75 + + if not pytest: + return + + np.testing.assert_almost_equal( + driver.directives.directive_list[0].chifact_start, 0.8888, decimal=3 + ) + np.testing.assert_almost_equal( + driver.directives.directive_list[0].chifact_target, 0.8888, decimal=3 + ) with open(workpath.parent / "inversion_test.ui.log", encoding="utf8") as file: content = file.read() - assert "Target Misfit: 3.00e+00 (3 data with chifact = 1.0)" in content - assert "IRLS Start Misfit: 3.00e+00 (3 data with chifact = 1.0)" in content + assert "Target Misfit: 8.00e+00 (8 data with chifact = 1.0)" in content + assert "IRLS Start Misfit: 8.00e+00 (8 data with chifact = 1.0)" in content assert driver.params.data_object.uid != gz.parent.uid assert np.all(np.isinf(driver.models.upper_bound)) @@ -173,11 +191,45 @@ def test_gravity_run( assert len(run_ws.get_entity("inversion_test.ui.log")) == 2 assert len(run_ws.get_entity("inversion_test.ui.out")) == 1 - if pytest: - check_target(output, target_run) - nan_ind = np.isnan(run_ws.get_entity("Iteration_0_model")[0].values) - inactive_ind = run_ws.get_entity("active_cells")[0].values == 0 - assert np.all(nan_ind == inactive_ind) + check_target(output, target_run) + nan_ind = np.isnan(run_ws.get_entity("Iteration_0_model")[0].values) + inactive_ind = run_ws.get_entity("active_cells")[0].values == 0 + assert np.all(nan_ind == inactive_ind) + + +def test_restart_run(tmp_path): + shutil.copy(tmp_path.parent / "test_gravity_run0" / "Inv_run.ui.json", tmp_path) + shutil.copy( + tmp_path.parent / "test_gravity_run0" / "inversion_test.ui.geoh5", tmp_path + ) + json_file = tmp_path / "Inv_run.ui.json" + + # Remember the last iteration + out_array = read_csv( + tmp_path.parent / "test_gravity_run0/inversion_test.ui.out", sep=" " + ) + + last_beta = out_array["beta"].iloc[-1] + last_phi_d = out_array["phi_d"].iloc[-1] + last_phi_m = out_array["phi_m"].iloc[-1] + + uijson = BaseUIJson.read(json_file) + uijson.geoh5 = tmp_path / "inversion_test.ui.geoh5" + uijson.set_values(max_global_iterations=5) + uijson.write(json_file) + GravityInversionDriver.start(json_file, start_iteration=-2) + + # Read the out file again and check against the previous full run + with Workspace(tmp_path / "inversion_test.ui.geoh5") as ws: + out_file = ws.get_entity("inversion_test.ui.out")[0] + out_array = read_csv(BytesIO(out_file.file_bytes), sep=" ") + np.testing.assert_almost_equal(out_array["beta"].iloc[4], last_beta, decimal=1) + np.testing.assert_almost_equal( + out_array["phi_d"].iloc[4], last_phi_d, decimal=1 + ) + np.testing.assert_almost_equal( + out_array["phi_m"].iloc[4], last_phi_m, decimal=1 + ) def test_array_too_large_run( diff --git a/tests/run_tests/driver_ip_2d_test.py b/tests/run_tests/driver_ip_2d_test.py index 0b2452f66..3365bef5e 100644 --- a/tests/run_tests/driver_ip_2d_test.py +++ b/tests/run_tests/driver_ip_2d_test.py @@ -39,7 +39,7 @@ # To test the full run and validate the inversion. # Move this file out of the test directory and run. -target_run = {"data_norm": 0.1236630897188764, "phi_d": 15800, "phi_m": 0.000171} +target_run = {"data_norm": 1.2313e-01, "phi_d": 1.5580e04, "phi_m": 2.2570e-04} def test_ip_2d_fwr_run( diff --git a/tests/run_tests/driver_ip_test.py b/tests/run_tests/driver_ip_test.py index 3d117a899..ca15138f0 100644 --- a/tests/run_tests/driver_ip_test.py +++ b/tests/run_tests/driver_ip_test.py @@ -37,7 +37,7 @@ # To test the full run and validate the inversion. # Move this file out of the test directory and run. -target_run = {"data_norm": 0.004782189506580897, "phi_d": 571, "phi_m": 1.55e-06} +target_run = {"data_norm": 4.6608e-03, "phi_d": 5.4190e02, "phi_m": 2.1450e-06} def test_ip_3d_fwr_run( diff --git a/tests/run_tests/driver_joint_cross_gradient_test.py b/tests/run_tests/driver_joint_cross_gradient_test.py index bc95bef57..630050fa1 100644 --- a/tests/run_tests/driver_joint_cross_gradient_test.py +++ b/tests/run_tests/driver_joint_cross_gradient_test.py @@ -9,14 +9,19 @@ # ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' import logging +import shutil +from io import BytesIO from pathlib import Path import numpy as np from geoh5py.groups import GroupTypeEnum, PropertyGroup from geoh5py.objects import CurrentElectrode, Octree, Points +from geoh5py.ui_json import BaseUIJson from geoh5py.workspace import Workspace +from pandas import read_csv from simpeg.directives import UpdateIRLS +from simpeg_drivers.driver import validate_out_group from simpeg_drivers.electricals.direct_current.three_dimensions.forward import ( DC3DForwardDriver, DC3DForwardOptions, @@ -65,7 +70,7 @@ # To test the full run and validate the inversion. # Move this file out of the test directory and run. -target_run = {"data_norm": 53.31485005157825, "phi_d": 562, "phi_m": 0.387} +target_run = {"data_norm": 5.3315e01, "phi_d": 4.9190e02, "phi_m": 2.7810e-01} INDUCING_FIELD = (50000.0, 90.0, 0.0) @@ -191,15 +196,16 @@ def test_joint_cross_gradient_fwr_run( def test_joint_cross_gradient_inv_run( tmp_path, - max_iterations=1, + max_iterations=3, pytest=True, ): workpath = tmp_path / "inversion_test.ui.geoh5" if pytest: - workpath = ( + shutil.copy( tmp_path.parent / "test_joint_cross_gradient_fwr_0" - / "inversion_test.ui.geoh5" + / "inversion_test.ui.geoh5", + tmp_path, ) with Workspace(workpath) as geoh5: @@ -307,6 +313,9 @@ def test_joint_cross_gradient_inv_run( sens_wts_threshold=1.0, percentile=100, ) + out_group = validate_out_group(joint_params) + joint_params.out_group = out_group + file = joint_params.write_ui_json(tmp_path / "Joint_Inv_run.ui.json") driver = JointCrossGradientDriver.start(file) @@ -325,13 +334,13 @@ def test_joint_cross_gradient_inv_run( # the scaling from its total misfit. np.testing.assert_allclose( driver.directives.scale_misfits.scalings, - [1.0, 0.755269, 0.5, 0.5, 0.675174], + [1.0, 0.423571, 0.125, 0.125, 0.300323], atol=1e-3, ) # Check that scaling * chi factor is reflected in data misfit multipliers np.testing.assert_allclose( driver.data_misfit.multipliers, - [0.8, 0.604215, 0.5, 0.5, 0.675174], + [0.8, 0.338857, 0.125, 0.125, 0.300323], atol=1e-3, ) @@ -345,6 +354,50 @@ def test_joint_cross_gradient_inv_run( check_target(output, target_run) +def test_restart_run(tmp_path): + shutil.copy( + tmp_path.parent / "test_joint_cross_gradient_inv_0" / "Joint_Inv_run.ui.json", + tmp_path, + ) + shutil.copy( + tmp_path.parent / "test_joint_cross_gradient_inv_0" / "inversion_test.ui.geoh5", + tmp_path, + ) + shutil.copy( + tmp_path.parent / "test_joint_cross_gradient_inv_0" / "inversion_test.ui.chi", + tmp_path, + ) + json_file = tmp_path / "Joint_Inv_run.ui.json" + + # Remember the last iteration + out_array = read_csv( + tmp_path.parent / "test_joint_cross_gradient_inv_0/inversion_test.ui.out", + sep=" ", + ) + + last_beta = out_array["beta"].iloc[-1] + last_phi_d = out_array["phi_d"].iloc[-1] + last_phi_m = out_array["phi_m"].iloc[-1] + + uijson = BaseUIJson.read(json_file) + uijson.geoh5 = tmp_path / "inversion_test.ui.geoh5" + uijson.set_values(max_global_iterations=5) + uijson.write(json_file) + JointCrossGradientDriver.start(json_file, start_iteration=-2) + + # Read the out file again and check against the previous full run + with Workspace(tmp_path / "inversion_test.ui.geoh5") as ws: + out_file = ws.get_entity("inversion_test.ui.out")[0] + out_array = read_csv(BytesIO(out_file.file_bytes), sep=" ") + np.testing.assert_almost_equal(out_array["beta"].iloc[5], last_beta, decimal=1) + np.testing.assert_almost_equal( + out_array["phi_d"].iloc[5], last_phi_d, decimal=0 + ) + np.testing.assert_almost_equal( + out_array["phi_m"].iloc[5], last_phi_m, decimal=0 + ) + + def test_joint_cross_gradient_rotated_run( tmp_path, caplog, @@ -451,10 +504,11 @@ def test_joint_cross_gradient_rotated_run( group_b_multiplier=1.0, ) - with caplog.at_level(logging.WARNING): - _ = JointCrossGradientDriver(joint_params) + with caplog.at_level(logging.WARNING): + driver = JointCrossGradientDriver(joint_params) + driver.initialize() - assert "Some drivers do not have a model" in caplog.text + assert "Some drivers do not have a model" in caplog.text # Add gradient rotation to the mvi driver and check it is used params.models.gradient_rotation = gradient_rotation @@ -471,9 +525,9 @@ def test_joint_cross_gradient_rotated_run( max_global_iterations=max_iterations, ) joint_driver = JointCrossGradientDriver(joint_params) - assert joint_driver.models.gradient_dip is not None joint_driver.run() + assert joint_driver.models.gradient_dip is not None if __name__ == "__main__": diff --git a/tests/run_tests/driver_joint_pgi_homogeneous_test.py b/tests/run_tests/driver_joint_pgi_homogeneous_test.py index 8d2acc00f..f45b90562 100644 --- a/tests/run_tests/driver_joint_pgi_homogeneous_test.py +++ b/tests/run_tests/driver_joint_pgi_homogeneous_test.py @@ -340,7 +340,7 @@ def test_homogeneous_run( max_irls_iterations=1, ) driver = JointPetrophysicsDriver(joint_params) - + driver.initialize() with pytest.raises( GeoAppsError, match="A reference model must be set and active on each" ): diff --git a/tests/run_tests/driver_joint_surveys_test.py b/tests/run_tests/driver_joint_surveys_test.py index 44537ba24..997909c25 100644 --- a/tests/run_tests/driver_joint_surveys_test.py +++ b/tests/run_tests/driver_joint_surveys_test.py @@ -8,14 +8,19 @@ # ' # ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +import shutil +from io import BytesIO from pathlib import Path import numpy as np -import pytest from geoh5py.objects import Octree +from geoh5py.ui_json import BaseUIJson from geoh5py.workspace import Workspace +from pandas import read_csv +from pytest import mark from simpeg.directives import SaveModelGeoH5, SavePropertyGroup +from simpeg_drivers.driver import validate_out_group from simpeg_drivers.electricals.direct_current.three_dimensions.inversion import ( DC3DInversionDriver, ) @@ -61,7 +66,7 @@ # To test the full run and validate the inversion. # Move this file out of the test directory and run. -target_run = {"data_norm": 0.5406566758729657, "phi_d": 1740, "phi_m": 137} +target_run = {"data_norm": 0.5406566758729658, "phi_d": 1437, "phi_m": 342.9} def test_joint_surveys_fwr_run( @@ -152,13 +157,14 @@ def test_joint_surveys_fwr_run( def test_joint_surveys_inv_run( tmp_path, - max_iterations=1, - unittest=True, + max_iterations=2, + pytest=True, ): workpath = tmp_path / "inversion_test.ui.geoh5" - if unittest: - workpath = ( - tmp_path.parent / "test_joint_surveys_fwr_run0" / "inversion_test.ui.geoh5" + if pytest: + shutil.copy( + tmp_path.parent / "test_joint_surveys_fwr_run0" / "inversion_test.ui.geoh5", + tmp_path, ) with Workspace(workpath) as geoh5: @@ -215,13 +221,16 @@ def test_joint_surveys_inv_run( auto_scale_misfits=True, ) - driver = JointSurveysDriver(joint_params) - driver.run() + out_group = validate_out_group(joint_params) + joint_params.out_group = out_group + joint_params.write_ui_json(path=tmp_path / "Inv_run.ui.json") + + driver = JointSurveysDriver.start(str(tmp_path / "Inv_run.ui.json")) # The rescaling is done evenly on the two tiles for both surveys np.testing.assert_allclose( driver.data_misfit.multipliers, - [1.0, 1.0, 0.9440, 0.9440], + [1.0, 1.0, 0.8967, 0.8967], atol=1e-3, ) @@ -231,11 +240,54 @@ def test_joint_surveys_inv_run( ) output["data"] = np.hstack(orig_data) - if unittest: + if pytest: check_target(output, target_run) -@pytest.mark.parametrize( +def test_restart_run(tmp_path): + shutil.copy( + tmp_path.parent / "test_joint_surveys_inv_run0" / "Inv_run.ui.json", tmp_path + ) + shutil.copy( + tmp_path.parent / "test_joint_surveys_inv_run0" / "inversion_test.ui.geoh5", + tmp_path, + ) + shutil.copy( + tmp_path.parent / "test_joint_surveys_inv_run0" / "inversion_test.ui.chi", + tmp_path, + ) + json_file = tmp_path / "Inv_run.ui.json" + + # Remember the last iteration + out_array = read_csv( + tmp_path.parent / "test_joint_surveys_inv_run0/inversion_test.ui.out", sep=" " + ) + + last_beta = out_array["beta"].iloc[-1] + last_phi_d = out_array["phi_d"].iloc[-1] + last_phi_m = out_array["phi_m"].iloc[-1] + + uijson = BaseUIJson.read(json_file) + uijson.geoh5 = tmp_path / "inversion_test.ui.geoh5" + uijson.set_values(max_global_iterations=4) + uijson.write(json_file) + JointSurveysDriver.start(json_file, start_iteration=-2) + + # Read the out file again and check against the previous full run + with Workspace(tmp_path / "inversion_test.ui.geoh5") as ws: + out_file = ws.get_entity("inversion_test.ui.out")[0] + out_array = read_csv(BytesIO(out_file.file_bytes), sep=" ") + np.testing.assert_almost_equal(out_array["beta"].iloc[4], last_beta, decimal=1) + np.testing.assert_almost_equal( + out_array["phi_d"].iloc[4], last_phi_d, decimal=1 + ) + np.testing.assert_almost_equal( + out_array["phi_m"].iloc[4], last_phi_m, decimal=1 + ) + + +# +@mark.parametrize( "option_class, driver_class", [ (MagneticVectorInversionOptions, MagneticVectorInversionDriver), @@ -315,6 +367,7 @@ def test_joint_surveys_mvi_run(tmp_path, option_class, driver_class, anomaly=0.0 ) driver = JointSurveysDriver(joint_params) + driver.initialize() assert np.isclose(driver.models.reference_model[0], 0) # Took it from driver_A assert driver.models.starting_model.shape == (driver.models.n_active * 3,) assert np.isclose( @@ -394,6 +447,7 @@ def test_joint_surveys_conductivity_run( ) driver = JointSurveysDriver(joint_params) + driver.initialize() assert np.isclose( driver.models.reference_model[0], np.log(1 / 5.0) ) # Took it from driver_A @@ -484,6 +538,7 @@ def test_joint_surveys_tem_run( ) driver = JointSurveysDriver(joint_params) + driver.initialize() assert ( len( [ @@ -507,5 +562,5 @@ def test_joint_surveys_tem_run( test_joint_surveys_inv_run( Path("./"), max_iterations=20, - unittest=False, + pytest=False, ) diff --git a/tests/run_tests/driver_mt_test.py b/tests/run_tests/driver_mt_test.py index c49afc7dd..7c5d8e79e 100644 --- a/tests/run_tests/driver_mt_test.py +++ b/tests/run_tests/driver_mt_test.py @@ -42,7 +42,7 @@ # To test the full run and validate the inversion. # Move this file out of the test directory and run. -target_run = {"data_norm": 0.003584600661140228, "phi_d": 4.45, "phi_m": 5.56} +target_run = {"data_norm": 3.4932e-02, "phi_d": 1.4260e-01, "phi_m": 3.3270e00} def setup_data(workspace, survey): diff --git a/tests/run_tests/driver_mvi_test.py b/tests/run_tests/driver_mvi_test.py index dceef5a02..04319d0c9 100644 --- a/tests/run_tests/driver_mvi_test.py +++ b/tests/run_tests/driver_mvi_test.py @@ -12,6 +12,8 @@ import contextlib import logging +import shutil +from io import BytesIO from pathlib import Path import numpy as np @@ -19,10 +21,13 @@ from geoh5py.groups import PropertyGroup from geoh5py.groups.property_group import GroupTypeEnum from geoh5py.objects import Curve +from geoh5py.ui_json import BaseUIJson from geoh5py.workspace import Workspace +from pandas import read_csv from simpeg.utils.mat_utils import cartesian2amplitude_dip_azimuth from simpeg_drivers.components.factories import DirectivesFactory +from simpeg_drivers.driver import validate_out_group from simpeg_drivers.potential_fields.magnetic_vector.forward import ( MagneticVectorForwardDriver, MagneticVectorForwardOptions, @@ -107,10 +112,11 @@ def test_magnetic_vector_run( ): workpath = tmp_path / "inversion_test.ui.geoh5" if pytest: - workpath = ( + shutil.copy( tmp_path.parent / "test_magnetic_vector_fwr_run0" - / "inversion_test.ui.geoh5" + / "inversion_test.ui.geoh5", + tmp_path, ) with Workspace(workpath) as geoh5: @@ -156,7 +162,10 @@ def test_magnetic_vector_run( max_global_iterations=max_iterations, initial_beta_ratio=5e-2, ) - params.write_ui_json(path=tmp_path / "Inv_run.ui.json") + + out_group = validate_out_group(params) + params.out_group = out_group + params.write_ui_json(path=tmp_path / "Inv_run.ui.json") if caplog: assert "Deprecated field 'lower_bound' will be ignored." in caplog.text @@ -187,6 +196,44 @@ def test_magnetic_vector_run( check_target(output, target_mvi_run) +def test_restart_run(tmp_path): + shutil.copy( + tmp_path.parent / "test_magnetic_vector_run0" / "Inv_run.ui.json", tmp_path + ) + shutil.copy( + tmp_path.parent / "test_magnetic_vector_run0" / "inversion_test.ui.geoh5", + tmp_path, + ) + json_file = tmp_path / "Inv_run.ui.json" + + # Remember the last iteration + out_array = read_csv( + tmp_path.parent / "test_magnetic_vector_run0/inversion_test.ui.out", sep=" " + ) + + last_beta = out_array["beta"].iloc[-2] + last_phi_d = out_array["phi_d"].iloc[-2] + last_phi_m = out_array["phi_m"].iloc[-2] + + uijson = BaseUIJson.read(json_file) + uijson.geoh5 = tmp_path / "inversion_test.ui.geoh5" + uijson.set_values(max_global_iterations=5) + uijson.write(json_file) + MagneticVectorInversionDriver.start(json_file, start_iteration=-4) + + # Read the out file again and check against the previous full run + with Workspace(tmp_path / "inversion_test.ui.geoh5") as ws: + out_file = ws.get_entity("inversion_test.ui.out")[0] + out_array = read_csv(BytesIO(out_file.file_bytes), sep=" ") + np.testing.assert_almost_equal(out_array["beta"].iloc[4], last_beta, decimal=1) + np.testing.assert_almost_equal( + out_array["phi_d"].iloc[4], last_phi_d, decimal=1 + ) + np.testing.assert_almost_equal( + out_array["phi_m"].iloc[4], last_phi_m, decimal=1 + ) + + def test_magnetic_vector_reference( tmp_path: Path, n_grid_points=3, diff --git a/tests/utils/runtests.py b/tests/utils/runtests.py new file mode 100644 index 000000000..cab10791c --- /dev/null +++ b/tests/utils/runtests.py @@ -0,0 +1,24 @@ +# ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +# Copyright (c) 2026 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 geoapps_utils.base import Driver, Options +from geoapps_utils.run import run_from_uijson + + +def run_driver_from_ui_json(params: Options, name: str = "runtest.ui.json") -> Driver: + """ + Serialize params to a ui.json file and run. + + :param params: Options instance to serialize. + :param name: Name of the ui.json file to write. + """ + path = params.geoh5.h5file.parent / name + params.write_ui_json(path) + return run_from_uijson(path) diff --git a/tests/utils/targets.py b/tests/utils/targets.py index 7d851636e..c1900d41f 100644 --- a/tests/utils/targets.py +++ b/tests/utils/targets.py @@ -51,7 +51,7 @@ def check_target(output: dict, target: dict, tolerance=0.05): :param tolerance: Tolerance between output and target measured as: |a-b|/b """ print( - f"Output: 'data_norm': {np.linalg.norm(output['data'])}, 'phi_d': {output['phi_d'][-1]}, 'phi_m': {output['phi_m'][-1]}" + f"Output: 'data_norm': {np.linalg.norm(output['data']):.4e}, 'phi_d': {output['phi_d'][-1]:.4e}, 'phi_m': {output['phi_m'][-1]:.4e}" ) print(f"Target: {target}")