From 4cf38219ec4bda08554f72438f35f71a1e623be0 Mon Sep 17 00:00:00 2001 From: domfournier Date: Tue, 22 Sep 2026 11:31:22 -0700 Subject: [PATCH 1/7] Allow drape models in 1D mesh ui --- simpeg_drivers-assets/uijson/fdem1d_forward.ui.json | 13 ++++++++----- .../uijson/fdem1d_inversion.ui.json | 13 ++++++++----- simpeg_drivers-assets/uijson/tdem1d_forward.ui.json | 13 ++++++++----- .../uijson/tdem1d_inversion.ui.json | 13 ++++++++----- 4 files changed, 32 insertions(+), 20 deletions(-) diff --git a/simpeg_drivers-assets/uijson/fdem1d_forward.ui.json b/simpeg_drivers-assets/uijson/fdem1d_forward.ui.json index d2fc8bdd..37faf245 100644 --- a/simpeg_drivers-assets/uijson/fdem1d_forward.ui.json +++ b/simpeg_drivers-assets/uijson/fdem1d_forward.ui.json @@ -49,7 +49,7 @@ "main": true, "enabled": true, "label": "Vertical core cell size (m)", - "value": 25.0 + "value": 10.0 }, "depth_core": { "min": 0.0, @@ -57,12 +57,12 @@ "main": true, "enabled": true, "label": "Depth of core (m)", - "value": 500.0 + "value": 100.0 }, "horizontal_padding": { "visible": false, "label": "Horizontal padding (m)", - "value": 1000.0 + "value": 100.0 }, "vertical_padding": { "min": 0.0, @@ -70,7 +70,7 @@ "main": true, "dependencyType": "disabled", "label": "Vertical padding (m)", - "value": 1000.0 + "value": 100.0 }, "expansion_factor": { "main": true, @@ -82,7 +82,10 @@ "group": "Mesh and models", "main": true, "label": "Mesh", - "meshType": "{4ea87376-3ece-438b-bf12-3479733ded46}", + "meshType": [ + "{c94968ea-cf7d-11eb-b8bc-0242ac130003}", + "{4ea87376-3ece-438b-bf12-3479733ded46}" + ], "value": "", "optional": true, "enabled": false, diff --git a/simpeg_drivers-assets/uijson/fdem1d_inversion.ui.json b/simpeg_drivers-assets/uijson/fdem1d_inversion.ui.json index 25602098..4783e753 100644 --- a/simpeg_drivers-assets/uijson/fdem1d_inversion.ui.json +++ b/simpeg_drivers-assets/uijson/fdem1d_inversion.ui.json @@ -99,7 +99,7 @@ "main": true, "enabled": true, "label": "Vertical core cell size (m)", - "value": 25.0 + "value": 10.0 }, "depth_core": { "min": 0.0, @@ -107,12 +107,12 @@ "main": true, "enabled": true, "label": "Depth of core (m)", - "value": 500.0 + "value": 100.0 }, "horizontal_padding": { "visible": false, "label": "Horizontal padding (m)", - "value": 1000.0 + "value": 100.0 }, "vertical_padding": { "min": 0.0, @@ -120,7 +120,7 @@ "main": true, "dependencyType": "disabled", "label": "Vertical padding (m)", - "value": 1000.0 + "value": 100.0 }, "expansion_factor": { "main": true, @@ -132,7 +132,10 @@ "group": "Mesh and models", "main": true, "label": "Mesh", - "meshType": "{4ea87376-3ece-438b-bf12-3479733ded46}", + "meshType": [ + "{c94968ea-cf7d-11eb-b8bc-0242ac130003}", + "{4ea87376-3ece-438b-bf12-3479733ded46}" + ], "value": "", "optional": true, "enabled": false, diff --git a/simpeg_drivers-assets/uijson/tdem1d_forward.ui.json b/simpeg_drivers-assets/uijson/tdem1d_forward.ui.json index f554d20d..f64e9016 100644 --- a/simpeg_drivers-assets/uijson/tdem1d_forward.ui.json +++ b/simpeg_drivers-assets/uijson/tdem1d_forward.ui.json @@ -54,7 +54,7 @@ "main": true, "enabled": true, "label": "Vertical core cell size (m)", - "value": 25.0 + "value": 10.0 }, "depth_core": { "min": 0.0, @@ -62,12 +62,12 @@ "main": true, "enabled": true, "label": "Depth of core (m)", - "value": 500.0 + "value": 100.0 }, "horizontal_padding": { "visible": false, "label": "Vertical padding (m)", - "value": 0.0 + "value": 100.0 }, "vertical_padding": { "min": 0.0, @@ -75,7 +75,7 @@ "main": true, "dependencyType": "disabled", "label": "Vertical padding (m)", - "value": 1000.0 + "value": 100.0 }, "expansion_factor": { "main": true, @@ -87,7 +87,10 @@ "group": "Mesh and models", "main": true, "label": "Mesh", - "meshType": "{4ea87376-3ece-438b-bf12-3479733ded46}", + "meshType": [ + "{c94968ea-cf7d-11eb-b8bc-0242ac130003}", + "{4ea87376-3ece-438b-bf12-3479733ded46}" + ], "value": "", "optional": true, "enabled": false, diff --git a/simpeg_drivers-assets/uijson/tdem1d_inversion.ui.json b/simpeg_drivers-assets/uijson/tdem1d_inversion.ui.json index a00eef9b..a3efe754 100644 --- a/simpeg_drivers-assets/uijson/tdem1d_inversion.ui.json +++ b/simpeg_drivers-assets/uijson/tdem1d_inversion.ui.json @@ -78,7 +78,7 @@ "main": true, "enabled": true, "label": "Vertical core cell size (m)", - "value": 25.0 + "value": 10.0 }, "depth_core": { "min": 0.0, @@ -86,12 +86,12 @@ "main": true, "enabled": true, "label": "Depth of core (m)", - "value": 500.0 + "value": 100.0 }, "horizontal_padding": { "visible": false, "label": "Vertical padding (m)", - "value": 0.0 + "value": 100.0 }, "vertical_padding": { "min": 0.0, @@ -99,7 +99,7 @@ "main": true, "dependencyType": "disabled", "label": "Vertical padding (m)", - "value": 1000.0 + "value": 100.0 }, "expansion_factor": { "main": true, @@ -111,7 +111,10 @@ "group": "Mesh and models", "main": true, "label": "Mesh", - "meshType": "{4ea87376-3ece-438b-bf12-3479733ded46}", + "meshType": [ + "{c94968ea-cf7d-11eb-b8bc-0242ac130003}", + "{4ea87376-3ece-438b-bf12-3479733ded46}" + ], "value": "", "optional": true, "enabled": false, From 2eabdc7a51eb7a94653b42e3678428698e3cc3e7 Mon Sep 17 00:00:00 2001 From: domfournier Date: Tue, 22 Sep 2026 12:05:11 -0700 Subject: [PATCH 2/7] Collect 1D mesh from input 3d mesh. Add restart test and adjust targets --- .../electromagnetics/base_1d_driver.py | 28 ++++++-- .../run_tests/driver_airborne_tem_1d_test.py | 67 +++++++++++++++---- 2 files changed, 76 insertions(+), 19 deletions(-) diff --git a/simpeg_drivers/electromagnetics/base_1d_driver.py b/simpeg_drivers/electromagnetics/base_1d_driver.py index af12b766..7aa447c3 100644 --- a/simpeg_drivers/electromagnetics/base_1d_driver.py +++ b/simpeg_drivers/electromagnetics/base_1d_driver.py @@ -20,7 +20,7 @@ from discretize.utils import mesh_utils from geoapps_utils.utils.locations import topo_drape_elevation from geoh5py import Workspace -from geoh5py.objects import Surface +from geoh5py.objects import DrapeModel, Octree, Surface from geoh5py.shared.merging.drape_model import DrapeModelMerger from geoh5py.ui_json.ui_json import fetch_active_workspace from numpy import ndarray @@ -29,6 +29,7 @@ from simpeg_drivers.components.meshes import InversionMesh from simpeg_drivers.driver import BaseDriver from simpeg_drivers.utils.utils import ( + drape_2_tensor, get_default_parallelization_params, xyz_2_drape_model, ) @@ -82,9 +83,23 @@ def inversion_mesh(self) -> InversionMesh: return self._inversion_mesh def get_1d_mesh(self) -> TensorMesh: + """ + Get a 1D mesh template from input parameters. + + If the input mesh is a DrapeModel, it is converted to a TensorMesh and extract the vertical cell sizes. + Otherwise, a new TensorMesh is created based on the drape model parameters. + """ + if isinstance(self.params.mesh, DrapeModel): + tensor = drape_2_tensor(self.params.mesh) + return TensorMesh([tensor.h[1]]) + + cell_size = self.params.drape_model.v_cell_size + if isinstance(self.params.mesh, Octree): + cell_size = self.params.mesh.w_cell_size + layers_mesh = mesh_utils.mesh_builder_xyz( np.c_[0], - np.r_[self.params.drape_model.v_cell_size], + np.r_[cell_size], padding_distance=[ [self.params.drape_model.vertical_padding, 0], ], @@ -140,10 +155,9 @@ def simulation(self): return self._simulation @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): """Overload configurations of BaseDriver Dask config settings.""" n_workers, n_threads = get_default_parallelization_params(json_path) - - super().start_dask_run(json_path, n_workers=n_workers, n_threads=n_threads) + kwargs["n_workers"] = n_workers + kwargs["n_threads"] = n_threads + super().start_dask_run(json_path, **kwargs) diff --git a/tests/run_tests/driver_airborne_tem_1d_test.py b/tests/run_tests/driver_airborne_tem_1d_test.py index 22efe36c..200cb5f6 100644 --- a/tests/run_tests/driver_airborne_tem_1d_test.py +++ b/tests/run_tests/driver_airborne_tem_1d_test.py @@ -10,10 +10,14 @@ from __future__ import annotations +import shutil +from io import BytesIO from pathlib import Path import numpy as np +from geoh5py.ui_json import UIJson from geoh5py.workspace import Workspace +from pandas import read_csv from simpeg_drivers.electromagnetics.time_domain_1d.forward import ( TDEM1DForwardDriver, @@ -37,14 +41,14 @@ # To test the full run and validate the inversion. # Move this file out of the test directory and run. -target_run = {"data_norm": 4.697209832464402e-10, "phi_d": 30.8, "phi_m": 82400} +target_run = {"data_norm": 4.1821e-10, "phi_d": 5.7170e01, "phi_m": 1.2470e04} def test_airborne_tem_1d_fwr_run( tmp_path: Path, n_grid_points=3, refinement=(2,), - cell_size=(20.0, 20.0, 20.0), + cell_size=(10.0, 10.0, 10.0), ): # Run the forward opts = SyntheticsComponentsOptions( @@ -64,7 +68,7 @@ def test_airborne_tem_1d_fwr_run( ), model=ModelOptions(background=0.1), ) - with get_workspace(tmp_path / "inversion_test.ui.geoh5") as geoh5: + with get_workspace(tmp_path / "inversion_test.geoh5") as geoh5: components = SyntheticsComponents( geoh5, options=opts, @@ -84,13 +88,13 @@ def test_airborne_tem_1d_fwr_run( fwr_driver.run() -def test_airborne_tem_1d_run(tmp_path: Path, max_iterations=1, pytest=True): - workpath = tmp_path / "inversion_test.ui.geoh5" +def test_airborne_tem_1d_run(tmp_path: Path, max_iterations=3, pytest=True): + workpath = tmp_path / "inversion_test.geoh5" + if pytest: - workpath = ( - tmp_path.parent - / "test_airborne_tem_1d_fwr_run0" - / "inversion_test.ui.geoh5" + shutil.copy( + tmp_path.parent / "test_airborne_tem_1d_fwr_run0" / "inversion_test.geoh5", + tmp_path, ) with Workspace(workpath) as geoh5: @@ -100,7 +104,7 @@ def test_airborne_tem_1d_run(tmp_path: Path, max_iterations=1, pytest=True): channels = { "vertical": "vertical", } - + mesh = geoh5.get_entity("Draped Model")[0] for chan, cname in channels.items(): data[cname] = [] uncertainties[f"{cname} uncertainties"] = [] @@ -137,7 +141,7 @@ def test_airborne_tem_1d_run(tmp_path: Path, max_iterations=1, pytest=True): # Run the inverse params = TDEM1DInversionOptions.build( geoh5=geoh5, - mesh=components.mesh, + mesh=mesh, topography_object=components.topography, data_object=components.survey, starting_model=5e-1, @@ -149,9 +153,11 @@ def test_airborne_tem_1d_run(tmp_path: Path, max_iterations=1, pytest=True): lower_bound=1e-4, upper_bound=1e2, max_global_iterations=max_iterations, - initial_beta_ratio=1e-2, + initial_beta_ratio=1e-0, + cooling_rate=1, **data_kwargs, ) + params.out_group = params.ui_json.to_ui_json_group(workspace=geoh5) params.write_ui_json(path=tmp_path / "Inv_run.ui.json") driver = TDEM1DInversionDriver(params) @@ -190,6 +196,43 @@ def test_airborne_tem_1d_run(tmp_path: Path, max_iterations=1, pytest=True): assert np.all(nan_ind == inactive_ind) +def test_restart_run(tmp_path): + shutil.copy( + tmp_path.parent / "test_airborne_tem_1d_run0" / "Inv_run.ui.json", tmp_path + ) + shutil.copy( + tmp_path.parent / "test_airborne_tem_1d_run0" / "inversion_test.geoh5", tmp_path + ) + json_file = tmp_path / "Inv_run.ui.json" + + # Remember the last iteration + out_array = read_csv( + tmp_path.parent / "test_airborne_tem_1d_run0/inversion_test.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 = UIJson.read(json_file) + uijson.geoh5 = tmp_path / "inversion_test.geoh5" + uijson.set_values(max_global_iterations=5) + uijson.write(json_file) + TDEM1DInversionDriver.start(json_file, start_iteration=-2) + + # Read the out file again and check against the previous full run + with Workspace(tmp_path / "inversion_test.geoh5") as ws: + out_file = ws.get_entity("inversion_test.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=1 + ) + np.testing.assert_almost_equal( + out_array["phi_m"].iloc[5], last_phi_m, decimal=1 + ) + + if __name__ == "__main__": # Full run test_airborne_tem_1d_fwr_run( From 3cd9db1d50e9e7a12c8206be4bcd634960ceb620 Mon Sep 17 00:00:00 2001 From: domfournier Date: Tue, 22 Sep 2026 12:06:47 -0700 Subject: [PATCH 3/7] Avoid over-writing n_workers and n_threads --- simpeg_drivers/electromagnetics/base_1d_driver.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/simpeg_drivers/electromagnetics/base_1d_driver.py b/simpeg_drivers/electromagnetics/base_1d_driver.py index 7aa447c3..7935d23e 100644 --- a/simpeg_drivers/electromagnetics/base_1d_driver.py +++ b/simpeg_drivers/electromagnetics/base_1d_driver.py @@ -158,6 +158,10 @@ def simulation(self): def start_dask_run(cls, json_path: Path, **kwargs): """Overload configurations of BaseDriver Dask config settings.""" n_workers, n_threads = get_default_parallelization_params(json_path) - kwargs["n_workers"] = n_workers - kwargs["n_threads"] = n_threads + + if "n_workers" not in kwargs: + kwargs["n_workers"] = n_workers + if "n_threads" not in kwargs: + kwargs["n_threads"] = n_threads + super().start_dask_run(json_path, **kwargs) From fa45f65529fedc018cf941d34c6b96f627556210 Mon Sep 17 00:00:00 2001 From: domfournier Date: Tue, 22 Sep 2026 12:18:26 -0700 Subject: [PATCH 4/7] Fix condition checks for n_workers and n_threads Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- simpeg_drivers/electromagnetics/base_1d_driver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/simpeg_drivers/electromagnetics/base_1d_driver.py b/simpeg_drivers/electromagnetics/base_1d_driver.py index 7935d23e..035f37c6 100644 --- a/simpeg_drivers/electromagnetics/base_1d_driver.py +++ b/simpeg_drivers/electromagnetics/base_1d_driver.py @@ -159,9 +159,9 @@ def start_dask_run(cls, json_path: Path, **kwargs): """Overload configurations of BaseDriver Dask config settings.""" n_workers, n_threads = get_default_parallelization_params(json_path) - if "n_workers" not in kwargs: + if kwargs.get("n_workers") is None: kwargs["n_workers"] = n_workers - if "n_threads" not in kwargs: + if kwargs.get("n_threads") is None: kwargs["n_threads"] = n_threads super().start_dask_run(json_path, **kwargs) From 07ce6e6719b3ed70dbe2fff63b5430bfbba3e1ea Mon Sep 17 00:00:00 2001 From: domfournier Date: Tue, 22 Sep 2026 12:19:04 -0700 Subject: [PATCH 5/7] Replace start with start_dask_run in tests Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tests/run_tests/driver_airborne_tem_1d_test.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/run_tests/driver_airborne_tem_1d_test.py b/tests/run_tests/driver_airborne_tem_1d_test.py index 200cb5f6..0594e1e2 100644 --- a/tests/run_tests/driver_airborne_tem_1d_test.py +++ b/tests/run_tests/driver_airborne_tem_1d_test.py @@ -218,7 +218,9 @@ def test_restart_run(tmp_path): uijson.geoh5 = tmp_path / "inversion_test.geoh5" uijson.set_values(max_global_iterations=5) uijson.write(json_file) - TDEM1DInversionDriver.start(json_file, start_iteration=-2) + TDEM1DInversionDriver.start_dask_run( + json_file, start_iteration=-2, n_workers=1, n_threads=1 + ) # Read the out file again and check against the previous full run with Workspace(tmp_path / "inversion_test.geoh5") as ws: From e373fcc0b6e1e33131cbe564e6571f6479a6d259 Mon Sep 17 00:00:00 2001 From: domfournier Date: Tue, 22 Sep 2026 13:22:13 -0700 Subject: [PATCH 6/7] Revert "Replace start with start_dask_run in tests" This reverts commit 07ce6e6719b3ed70dbe2fff63b5430bfbba3e1ea. --- tests/run_tests/driver_airborne_tem_1d_test.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/run_tests/driver_airborne_tem_1d_test.py b/tests/run_tests/driver_airborne_tem_1d_test.py index 0594e1e2..200cb5f6 100644 --- a/tests/run_tests/driver_airborne_tem_1d_test.py +++ b/tests/run_tests/driver_airborne_tem_1d_test.py @@ -218,9 +218,7 @@ def test_restart_run(tmp_path): uijson.geoh5 = tmp_path / "inversion_test.geoh5" uijson.set_values(max_global_iterations=5) uijson.write(json_file) - TDEM1DInversionDriver.start_dask_run( - json_file, start_iteration=-2, n_workers=1, n_threads=1 - ) + TDEM1DInversionDriver.start(json_file, start_iteration=-2) # Read the out file again and check against the previous full run with Workspace(tmp_path / "inversion_test.geoh5") as ws: From c1d11c77af74a5601d873d24a86283800fbe2beb Mon Sep 17 00:00:00 2001 From: domfournier Date: Tue, 22 Sep 2026 13:52:41 -0700 Subject: [PATCH 7/7] Update fem test --- tests/run_tests/driver_airborne_fem_1d_test.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/run_tests/driver_airborne_fem_1d_test.py b/tests/run_tests/driver_airborne_fem_1d_test.py index 5915c4e4..d872a1b3 100644 --- a/tests/run_tests/driver_airborne_fem_1d_test.py +++ b/tests/run_tests/driver_airborne_fem_1d_test.py @@ -41,14 +41,14 @@ # To test the full run and validate the inversion. # Move this file out of the test directory and run. -target_run = {"data_norm": 381.0495293422212, "phi_d": 33400, "phi_m": 198} +target_run = {"data_norm": 5.5379e02, "phi_d": 4.5220e04, "phi_m": 2.2230e02} def test_fem_fwr_1d_run( tmp_path: Path, n_grid_points=3, refinement=(2,), - cell_size=(20.0, 20.0, 20.0), + cell_size=(10.0, 10.0, 10.0), ): # Run the forward opts = SyntheticsComponentsOptions( @@ -68,7 +68,7 @@ def test_fem_fwr_1d_run( ), model=ModelOptions(background=1e-4, anomaly=0.1), ) - with get_workspace(tmp_path / "inversion_test.ui.geoh5") as geoh5: + with get_workspace(tmp_path / "inversion_test.geoh5") as geoh5: components = SyntheticsComponents(geoh5=geoh5, options=opts) params = FDEM1DForwardOptions.build( geoh5=geoh5, @@ -85,9 +85,9 @@ def test_fem_fwr_1d_run( def test_fem_1d_run(tmp_path: Path, max_iterations=1, pytest=True): - workpath = tmp_path / "inversion_test.ui.geoh5" + workpath = tmp_path / "inversion_test.geoh5" if pytest: - workpath = tmp_path.parent / "test_fem_fwr_1d_run0" / "inversion_test.ui.geoh5" + workpath = tmp_path.parent / "test_fem_fwr_1d_run0" / "inversion_test.geoh5" with Workspace(workpath) as geoh5: components = SyntheticsComponents(geoh5) @@ -97,7 +97,7 @@ def test_fem_1d_run(tmp_path: Path, max_iterations=1, pytest=True): "real": "real", "imag": "imag", } - + mesh = geoh5.get_entity("Draped Model")[0] for chan, cname in channels.items(): data[cname] = [] uncertainties[f"{cname} uncertainties"] = [] @@ -136,7 +136,7 @@ def test_fem_1d_run(tmp_path: Path, max_iterations=1, pytest=True): # Run the inverse params = FDEM1DInversionOptions.build( geoh5=geoh5, - mesh=components.mesh, + mesh=mesh, topography_object=components.topography, data_object=components.survey, starting_model=1e-3,