From ab48c34dc5996dfb5f380632300b27b8da29af6e Mon Sep 17 00:00:00 2001 From: domfournier Date: Wed, 1 Apr 2026 10:35:23 -0700 Subject: [PATCH 01/24] Fix issue with tile_spatial --- .../uijson/apparent_conductivity_forward.ui.json | 3 --- .../uijson/apparent_conductivity_inversion.ui.json | 3 --- .../uijson/direct_current_3d_forward.ui.json | 3 --- .../uijson/direct_current_3d_inversion.ui.json | 3 --- simpeg_drivers-assets/uijson/fdem1d_forward.ui.json | 3 --- simpeg_drivers-assets/uijson/fdem1d_inversion.ui.json | 3 --- simpeg_drivers-assets/uijson/fdem_forward.ui.json | 3 --- simpeg_drivers-assets/uijson/fdem_inversion.ui.json | 3 --- simpeg_drivers-assets/uijson/gravity_forward.ui.json | 3 --- .../uijson/gravity_inversion.ui.json | 8 -------- .../uijson/induced_polarization_2d_inversion.ui.json | 11 ++++++++++- .../uijson/induced_polarization_3d_forward.ui.json | 3 --- .../uijson/induced_polarization_3d_inversion.ui.json | 3 --- .../uijson/magnetic_scalar_forward.ui.json | 6 +----- .../uijson/magnetic_scalar_inversion.ui.json | 6 +----- .../uijson/magnetic_vector_forward.ui.json | 6 ++---- .../uijson/magnetic_vector_inversion.ui.json | 6 +----- .../uijson/magnetotellurics_forward.ui.json | 3 --- .../uijson/magnetotellurics_inversion.ui.json | 3 --- simpeg_drivers-assets/uijson/tdem1d_forward.ui.json | 3 --- simpeg_drivers-assets/uijson/tdem1d_inversion.ui.json | 3 --- simpeg_drivers-assets/uijson/tdem_forward.ui.json | 3 --- simpeg_drivers-assets/uijson/tdem_inversion.ui.json | 3 --- simpeg_drivers-assets/uijson/tipper_forward.ui.json | 3 --- simpeg_drivers-assets/uijson/tipper_inversion.ui.json | 3 --- 25 files changed, 15 insertions(+), 85 deletions(-) diff --git a/simpeg_drivers-assets/uijson/apparent_conductivity_forward.ui.json b/simpeg_drivers-assets/uijson/apparent_conductivity_forward.ui.json index 0007c916..b22c6220 100644 --- a/simpeg_drivers-assets/uijson/apparent_conductivity_forward.ui.json +++ b/simpeg_drivers-assets/uijson/apparent_conductivity_forward.ui.json @@ -129,9 +129,6 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", "value": 1, "min": 1, "max": 1000, diff --git a/simpeg_drivers-assets/uijson/apparent_conductivity_inversion.ui.json b/simpeg_drivers-assets/uijson/apparent_conductivity_inversion.ui.json index 145a3cd4..de2d0d7a 100644 --- a/simpeg_drivers-assets/uijson/apparent_conductivity_inversion.ui.json +++ b/simpeg_drivers-assets/uijson/apparent_conductivity_inversion.ui.json @@ -514,9 +514,6 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", "value": 1, "min": 1, "max": 1000, diff --git a/simpeg_drivers-assets/uijson/direct_current_3d_forward.ui.json b/simpeg_drivers-assets/uijson/direct_current_3d_forward.ui.json index ce8f6673..d665b31d 100644 --- a/simpeg_drivers-assets/uijson/direct_current_3d_forward.ui.json +++ b/simpeg_drivers-assets/uijson/direct_current_3d_forward.ui.json @@ -123,9 +123,6 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", "value": 1, "min": 1, "max": 1000, diff --git a/simpeg_drivers-assets/uijson/direct_current_3d_inversion.ui.json b/simpeg_drivers-assets/uijson/direct_current_3d_inversion.ui.json index a87752ae..40f770f7 100644 --- a/simpeg_drivers-assets/uijson/direct_current_3d_inversion.ui.json +++ b/simpeg_drivers-assets/uijson/direct_current_3d_inversion.ui.json @@ -499,9 +499,6 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", "value": 1, "min": 1, "max": 1000, diff --git a/simpeg_drivers-assets/uijson/fdem1d_forward.ui.json b/simpeg_drivers-assets/uijson/fdem1d_forward.ui.json index 11512518..e769de08 100644 --- a/simpeg_drivers-assets/uijson/fdem1d_forward.ui.json +++ b/simpeg_drivers-assets/uijson/fdem1d_forward.ui.json @@ -178,9 +178,6 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", "value": 1, "min": 1, "max": 1000, diff --git a/simpeg_drivers-assets/uijson/fdem1d_inversion.ui.json b/simpeg_drivers-assets/uijson/fdem1d_inversion.ui.json index 632e65b8..bbfebf8f 100644 --- a/simpeg_drivers-assets/uijson/fdem1d_inversion.ui.json +++ b/simpeg_drivers-assets/uijson/fdem1d_inversion.ui.json @@ -538,9 +538,6 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", "value": 1, "min": 1, "max": 1000, diff --git a/simpeg_drivers-assets/uijson/fdem_forward.ui.json b/simpeg_drivers-assets/uijson/fdem_forward.ui.json index a60b92c8..7bfc2251 100644 --- a/simpeg_drivers-assets/uijson/fdem_forward.ui.json +++ b/simpeg_drivers-assets/uijson/fdem_forward.ui.json @@ -188,9 +188,6 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", "value": 1, "min": 1, "max": 1000, diff --git a/simpeg_drivers-assets/uijson/fdem_inversion.ui.json b/simpeg_drivers-assets/uijson/fdem_inversion.ui.json index ece2e082..65ca85a2 100644 --- a/simpeg_drivers-assets/uijson/fdem_inversion.ui.json +++ b/simpeg_drivers-assets/uijson/fdem_inversion.ui.json @@ -697,9 +697,6 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", "value": 1, "min": 1, "max": 1000, diff --git a/simpeg_drivers-assets/uijson/gravity_forward.ui.json b/simpeg_drivers-assets/uijson/gravity_forward.ui.json index b844d141..6b27f175 100644 --- a/simpeg_drivers-assets/uijson/gravity_forward.ui.json +++ b/simpeg_drivers-assets/uijson/gravity_forward.ui.json @@ -170,9 +170,6 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", "value": 1, "min": 1, "max": 1000, diff --git a/simpeg_drivers-assets/uijson/gravity_inversion.ui.json b/simpeg_drivers-assets/uijson/gravity_inversion.ui.json index 37e94675..8f6a79dc 100644 --- a/simpeg_drivers-assets/uijson/gravity_inversion.ui.json +++ b/simpeg_drivers-assets/uijson/gravity_inversion.ui.json @@ -758,14 +758,6 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", - "dataType": "Float", - "association": [ - "Vertex", - "Cell" - ], "value": 1, "min": 1, "max": 1000, diff --git a/simpeg_drivers-assets/uijson/induced_polarization_2d_inversion.ui.json b/simpeg_drivers-assets/uijson/induced_polarization_2d_inversion.ui.json index 051734a3..0473e306 100644 --- a/simpeg_drivers-assets/uijson/induced_polarization_2d_inversion.ui.json +++ b/simpeg_drivers-assets/uijson/induced_polarization_2d_inversion.ui.json @@ -537,7 +537,16 @@ "tooltip": "Direct solver to use for the forward calculations", "value": "Pardiso" }, - "tile_spatial": 1, + "tile_spatial": { + "group": "Compute", + "label": "Number of tiles", + "value": 1, + "min": 1, + "max": 1000, + "verbose": 2, + "visible": false, + "tooltip": "Splits the objective function into spatial tiles for distributed computation using the Dask library" + }, "store_sensitivities": { "choiceList": [ "disk", diff --git a/simpeg_drivers-assets/uijson/induced_polarization_3d_forward.ui.json b/simpeg_drivers-assets/uijson/induced_polarization_3d_forward.ui.json index d4da081f..0a5c387c 100644 --- a/simpeg_drivers-assets/uijson/induced_polarization_3d_forward.ui.json +++ b/simpeg_drivers-assets/uijson/induced_polarization_3d_forward.ui.json @@ -139,9 +139,6 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", "value": 1, "min": 1, "max": 1000, diff --git a/simpeg_drivers-assets/uijson/induced_polarization_3d_inversion.ui.json b/simpeg_drivers-assets/uijson/induced_polarization_3d_inversion.ui.json index 9ef7b3f8..00c37107 100644 --- a/simpeg_drivers-assets/uijson/induced_polarization_3d_inversion.ui.json +++ b/simpeg_drivers-assets/uijson/induced_polarization_3d_inversion.ui.json @@ -515,9 +515,6 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", "value": 1, "min": 1, "max": 1000, diff --git a/simpeg_drivers-assets/uijson/magnetic_scalar_forward.ui.json b/simpeg_drivers-assets/uijson/magnetic_scalar_forward.ui.json index c4b5d03b..723cba86 100644 --- a/simpeg_drivers-assets/uijson/magnetic_scalar_forward.ui.json +++ b/simpeg_drivers-assets/uijson/magnetic_scalar_forward.ui.json @@ -98,8 +98,7 @@ "{f26feba3-aded-494b-b9e9-b2bbcbe298e1}", "{48f5054a-1c5c-4ca4-9048-80f36dc60a06}", "{b020a277-90e2-4cd7-84d6-612ee3f25051}", - "{4b99204c-d133-4579-a916-a9c8b98cfccb}", - "{028e4905-cc97-4dab-b1bf-d76f58b501b5}" + "{4b99204c-d133-4579-a916-a9c8b98cfccb}" ], "value": "" }, @@ -200,9 +199,6 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", "value": 1, "min": 1, "max": 1000, diff --git a/simpeg_drivers-assets/uijson/magnetic_scalar_inversion.ui.json b/simpeg_drivers-assets/uijson/magnetic_scalar_inversion.ui.json index f025304d..3fdc6117 100644 --- a/simpeg_drivers-assets/uijson/magnetic_scalar_inversion.ui.json +++ b/simpeg_drivers-assets/uijson/magnetic_scalar_inversion.ui.json @@ -47,8 +47,7 @@ "{f26feba3-aded-494b-b9e9-b2bbcbe298e1}", "{48f5054a-1c5c-4ca4-9048-80f36dc60a06}", "{b020a277-90e2-4cd7-84d6-612ee3f25051}", - "{4b99204c-d133-4579-a916-a9c8b98cfccb}", - "{028e4905-cc97-4dab-b1bf-d76f58b501b5}" + "{4b99204c-d133-4579-a916-a9c8b98cfccb}" ], "value": "" }, @@ -791,9 +790,6 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", "value": 1, "min": 1, "max": 1000, diff --git a/simpeg_drivers-assets/uijson/magnetic_vector_forward.ui.json b/simpeg_drivers-assets/uijson/magnetic_vector_forward.ui.json index 0020bbdc..c2f2d31d 100644 --- a/simpeg_drivers-assets/uijson/magnetic_vector_forward.ui.json +++ b/simpeg_drivers-assets/uijson/magnetic_vector_forward.ui.json @@ -46,7 +46,8 @@ "{6a057fdc-b355-11e3-95be-fd84a7ffcb88}", "{f26feba3-aded-494b-b9e9-b2bbcbe298e1}", "{48f5054a-1c5c-4ca4-9048-80f36dc60a06}", - "{b020a277-90e2-4cd7-84d6-612ee3f25051}" + "{b020a277-90e2-4cd7-84d6-612ee3f25051}", + "{4b99204c-d133-4579-a916-a9c8b98cfccb}" ], "value": "", "optional": true, @@ -232,9 +233,6 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", "value": 1, "min": 1, "max": 1000, diff --git a/simpeg_drivers-assets/uijson/magnetic_vector_inversion.ui.json b/simpeg_drivers-assets/uijson/magnetic_vector_inversion.ui.json index 0060ded7..38103023 100644 --- a/simpeg_drivers-assets/uijson/magnetic_vector_inversion.ui.json +++ b/simpeg_drivers-assets/uijson/magnetic_vector_inversion.ui.json @@ -47,8 +47,7 @@ "{f26feba3-aded-494b-b9e9-b2bbcbe298e1}", "{48f5054a-1c5c-4ca4-9048-80f36dc60a06}", "{b020a277-90e2-4cd7-84d6-612ee3f25051}", - "{4b99204c-d133-4579-a916-a9c8b98cfccb}", - "{028e4905-cc97-4dab-b1bf-d76f58b501b5}" + "{4b99204c-d133-4579-a916-a9c8b98cfccb}" ], "value": "" }, @@ -839,9 +838,6 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", "value": 1, "min": 1, "max": 1000, diff --git a/simpeg_drivers-assets/uijson/magnetotellurics_forward.ui.json b/simpeg_drivers-assets/uijson/magnetotellurics_forward.ui.json index 2790747c..4fe8895e 100644 --- a/simpeg_drivers-assets/uijson/magnetotellurics_forward.ui.json +++ b/simpeg_drivers-assets/uijson/magnetotellurics_forward.ui.json @@ -177,9 +177,6 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", "value": 1, "min": 1, "max": 1000, diff --git a/simpeg_drivers-assets/uijson/magnetotellurics_inversion.ui.json b/simpeg_drivers-assets/uijson/magnetotellurics_inversion.ui.json index e782cb52..78236b9a 100644 --- a/simpeg_drivers-assets/uijson/magnetotellurics_inversion.ui.json +++ b/simpeg_drivers-assets/uijson/magnetotellurics_inversion.ui.json @@ -728,9 +728,6 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", "value": 1, "min": 1, "max": 1000, diff --git a/simpeg_drivers-assets/uijson/tdem1d_forward.ui.json b/simpeg_drivers-assets/uijson/tdem1d_forward.ui.json index ec596baf..12e09787 100644 --- a/simpeg_drivers-assets/uijson/tdem1d_forward.ui.json +++ b/simpeg_drivers-assets/uijson/tdem1d_forward.ui.json @@ -191,9 +191,6 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", "value": 1, "min": 1, "max": 1000, diff --git a/simpeg_drivers-assets/uijson/tdem1d_inversion.ui.json b/simpeg_drivers-assets/uijson/tdem1d_inversion.ui.json index 72c09b74..5750fcaa 100644 --- a/simpeg_drivers-assets/uijson/tdem1d_inversion.ui.json +++ b/simpeg_drivers-assets/uijson/tdem1d_inversion.ui.json @@ -519,9 +519,6 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", "value": 1, "min": 1, "max": 1000, diff --git a/simpeg_drivers-assets/uijson/tdem_forward.ui.json b/simpeg_drivers-assets/uijson/tdem_forward.ui.json index 287391f8..d0e3fea7 100644 --- a/simpeg_drivers-assets/uijson/tdem_forward.ui.json +++ b/simpeg_drivers-assets/uijson/tdem_forward.ui.json @@ -184,9 +184,6 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", "value": 1, "min": 1, "max": 1000, diff --git a/simpeg_drivers-assets/uijson/tdem_inversion.ui.json b/simpeg_drivers-assets/uijson/tdem_inversion.ui.json index 18e93a52..624b1c10 100644 --- a/simpeg_drivers-assets/uijson/tdem_inversion.ui.json +++ b/simpeg_drivers-assets/uijson/tdem_inversion.ui.json @@ -601,9 +601,6 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", "value": 1, "min": 1, "max": 1000, diff --git a/simpeg_drivers-assets/uijson/tipper_forward.ui.json b/simpeg_drivers-assets/uijson/tipper_forward.ui.json index 7794a830..76d5d7c0 100644 --- a/simpeg_drivers-assets/uijson/tipper_forward.ui.json +++ b/simpeg_drivers-assets/uijson/tipper_forward.ui.json @@ -153,9 +153,6 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", "value": 1, "min": 1, "max": 1000, diff --git a/simpeg_drivers-assets/uijson/tipper_inversion.ui.json b/simpeg_drivers-assets/uijson/tipper_inversion.ui.json index 9c85c829..e9f65bba 100644 --- a/simpeg_drivers-assets/uijson/tipper_inversion.ui.json +++ b/simpeg_drivers-assets/uijson/tipper_inversion.ui.json @@ -608,9 +608,6 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", "value": 1, "min": 1, "max": 1000, From d63c55d1f010dbd2267a19d5311bbfbb59ab9067 Mon Sep 17 00:00:00 2001 From: domfournier Date: Wed, 1 Apr 2026 10:52:27 -0700 Subject: [PATCH 02/24] Add icons to Option classes --- .../depth_of_investigation/sensitivity_cutoff/options.py | 1 + .../electricals/direct_current/three_dimensions/options.py | 2 ++ .../electricals/direct_current/two_dimensions/options.py | 3 ++- .../induced_polarization/three_dimensions/options.py | 4 ++-- .../induced_polarization/two_dimensions/options.py | 2 ++ simpeg_drivers/electromagnetics/frequency_domain/options.py | 2 ++ .../electromagnetics/frequency_domain_1d/options.py | 2 ++ simpeg_drivers/electromagnetics/time_domain/options.py | 2 ++ simpeg_drivers/electromagnetics/time_domain_1d/options.py | 2 ++ simpeg_drivers/joint/joint_cross_gradient/options.py | 1 + simpeg_drivers/joint/joint_petrophysics/options.py | 1 + simpeg_drivers/joint/joint_surveys/options.py | 1 + .../natural_sources/apparent_conductivity/options.py | 2 ++ simpeg_drivers/natural_sources/magnetotellurics/options.py | 2 ++ simpeg_drivers/natural_sources/tipper/options.py | 2 ++ simpeg_drivers/plate_simulation/match/options.py | 1 + simpeg_drivers/plate_simulation/options.py | 1 + simpeg_drivers/plate_simulation/sweep/options.py | 1 + simpeg_drivers/potential_fields/gravity/options.py | 2 ++ simpeg_drivers/potential_fields/magnetic_scalar/options.py | 2 ++ simpeg_drivers/potential_fields/magnetic_vector/options.py | 2 ++ simpeg_drivers/utils/tile_estimate.py | 1 + 22 files changed, 36 insertions(+), 3 deletions(-) diff --git a/simpeg_drivers/depth_of_investigation/sensitivity_cutoff/options.py b/simpeg_drivers/depth_of_investigation/sensitivity_cutoff/options.py index a284eb41..f70cec3e 100644 --- a/simpeg_drivers/depth_of_investigation/sensitivity_cutoff/options.py +++ b/simpeg_drivers/depth_of_investigation/sensitivity_cutoff/options.py @@ -37,6 +37,7 @@ class SensitivityCutoffOptions(Options): ) title: str = "Depth of Investigation: Sensitivity Cutoff" + icon: str = "grd" run_command: str = "simpeg_drivers.depth_of_investigation.sensitivity_cutoff.driver" conda_environment: str = "simpeg_drivers" diff --git a/simpeg_drivers/electricals/direct_current/three_dimensions/options.py b/simpeg_drivers/electricals/direct_current/three_dimensions/options.py index 728fa663..259a4260 100644 --- a/simpeg_drivers/electricals/direct_current/three_dimensions/options.py +++ b/simpeg_drivers/electricals/direct_current/three_dimensions/options.py @@ -40,6 +40,7 @@ class DC3DForwardOptions(BaseForwardOptions): "simpeg_drivers.electricals.direct_current.three_dimensions.forward" ) title: str = "Direct Current 3D Forward" + icon: str = "PotentialElectrode" physical_property: str = "conductivity" inversion_type: str = "direct current 3d" @@ -64,6 +65,7 @@ class DC3DInversionOptions(BaseInversionOptions): "simpeg_drivers.electricals.direct_current.three_dimensions.inversion" ) title: str = "Direct Current 3D Inversion" + icon: str = "PotentialElectrode" physical_property: str = "conductivity" inversion_type: str = "direct current 3d" diff --git a/simpeg_drivers/electricals/direct_current/two_dimensions/options.py b/simpeg_drivers/electricals/direct_current/two_dimensions/options.py index d60b882a..8b850438 100644 --- a/simpeg_drivers/electricals/direct_current/two_dimensions/options.py +++ b/simpeg_drivers/electricals/direct_current/two_dimensions/options.py @@ -38,6 +38,7 @@ class DC2DForwardOptions(BaseForwardOptions, Base2DOptions): ) title: str = "Direct Current 2D Forward" + icon: str = "PotentialElectrode" physical_property: str = "conductivity" inversion_type: str = "direct current 2d" @@ -59,7 +60,7 @@ class DC2DInversionOptions(BaseInversionOptions, Base2DOptions): default_ui_json: ClassVar[Path] = ( assets_path() / "uijson/direct_current_2d_inversion.ui.json" ) - + icon: str = "PotentialElectrode" title: str = "Direct Current 2D Inversion" physical_property: str = "conductivity" inversion_type: str = "direct current 2d" diff --git a/simpeg_drivers/electricals/induced_polarization/three_dimensions/options.py b/simpeg_drivers/electricals/induced_polarization/three_dimensions/options.py index 780e256a..b9449a5d 100644 --- a/simpeg_drivers/electricals/induced_polarization/three_dimensions/options.py +++ b/simpeg_drivers/electricals/induced_polarization/three_dimensions/options.py @@ -36,8 +36,8 @@ class IP3DForwardOptions(BaseForwardOptions): default_ui_json: ClassVar[Path] = ( assets_path() / "uijson/induced_polarization_3d_forward.ui.json" ) - title: str = "Induced Polarization 3D Forward" + icon: str = "PotentialElectrode" physical_property: str = "chargeability" inversion_type: str = "induced polarization 3d" @@ -58,8 +58,8 @@ class IP3DInversionOptions(BaseInversionOptions): default_ui_json: ClassVar[Path] = ( assets_path() / "uijson/induced_polarization_3d_inversion.ui.json" ) - title: str = "Induced Polarization 3D Inversion" + icon: str = "PotentialElectrode" physical_property: str = "chargeability" inversion_type: str = "induced polarization 3d" diff --git a/simpeg_drivers/electricals/induced_polarization/two_dimensions/options.py b/simpeg_drivers/electricals/induced_polarization/two_dimensions/options.py index b92fcfcd..444caafe 100644 --- a/simpeg_drivers/electricals/induced_polarization/two_dimensions/options.py +++ b/simpeg_drivers/electricals/induced_polarization/two_dimensions/options.py @@ -39,6 +39,7 @@ class IP2DForwardOptions(BaseForwardOptions, Base2DOptions): ) title: str = "Induced Polarization 2D Forward" + icon: str = "PotentialElectrode" physical_property: str = "chargeability" inversion_type: str = "induced polarization 2d" @@ -61,6 +62,7 @@ class IP2DInversionOptions(BaseInversionOptions, Base2DOptions): ) title: str = "Induced Polarization 2D Inversion" + icon: str = "PotentialElectrode" physical_property: str = "chargeability" inversion_type: str = "induced polarization 2d" diff --git a/simpeg_drivers/electromagnetics/frequency_domain/options.py b/simpeg_drivers/electromagnetics/frequency_domain/options.py index f420d8aa..b04246eb 100644 --- a/simpeg_drivers/electromagnetics/frequency_domain/options.py +++ b/simpeg_drivers/electromagnetics/frequency_domain/options.py @@ -99,6 +99,7 @@ class FDEMForwardOptions(BaseForwardOptions, BaseFDEMOptions): default_ui_json: ClassVar[Path] = assets_path() / "uijson/fdem_forward.ui.json" run_command: str = "simpeg_drivers.electromagnetics.frequency_domain.forward" title: str = "Frequency-domain EM (FEM) Forward" + icon: str = "surveyairborneem" physical_property: str = "conductivity" inversion_type: str = "fdem" @@ -140,6 +141,7 @@ class FDEMInversionOptions(BaseFDEMOptions, BaseInversionOptions): default_ui_json: ClassVar[Path] = assets_path() / "uijson/fdem_inversion.ui.json" run_command: str = "simpeg_drivers.electromagnetics.frequency_domain.inversion" title: str = "Frequency-domain EM (FEM) Inversion" + icon: str = "surveyairborneem" physical_property: str = "conductivity" inversion_type: str = "fdem" diff --git a/simpeg_drivers/electromagnetics/frequency_domain_1d/options.py b/simpeg_drivers/electromagnetics/frequency_domain_1d/options.py index e9544370..829cc3e5 100644 --- a/simpeg_drivers/electromagnetics/frequency_domain_1d/options.py +++ b/simpeg_drivers/electromagnetics/frequency_domain_1d/options.py @@ -41,6 +41,7 @@ class FDEM1DForwardOptions(BaseForwardOptions, BaseFDEMOptions, Base1DOptions): default_ui_json: ClassVar[Path] = assets_path() / "uijson/fdem1d_forward.ui.json" run_command: str = "simpeg_drivers.electromagnetics.frequency_domain_1d.forward" title: str = "Frequency-domain EM-1D (FEM-1D) Forward" + icon: str = "surveyairborneem" physical_property: str = "conductivity" inversion_type: str = "fdem 1d" data_object: AirborneFEMReceivers @@ -64,6 +65,7 @@ class FDEM1DInversionOptions(BaseFDEMOptions, BaseInversionOptions, Base1DOption default_ui_json: ClassVar[Path] = assets_path() / "uijson/fdem1d_inversion.ui.json" run_command: str = "simpeg_drivers.electromagnetics.frequency_domain_1d.inversion" title: str = "Frequency-domain EM-1D (FEM-1D) Inversion" + icon: str = "surveyairborneem" physical_property: str = "conductivity" inversion_type: str = "fdem 1d" diff --git a/simpeg_drivers/electromagnetics/time_domain/options.py b/simpeg_drivers/electromagnetics/time_domain/options.py index 62295127..90d34e5c 100644 --- a/simpeg_drivers/electromagnetics/time_domain/options.py +++ b/simpeg_drivers/electromagnetics/time_domain/options.py @@ -86,6 +86,7 @@ class TDEMForwardOptions(BaseTDEMOptions, BaseForwardOptions): run_command: str = "simpeg_drivers.electromagnetics.time_domain.forward" title: str = "Time-domain EM (TEM) Forward" + icon: str = "surveyairborneem" physical_property: str = "conductivity" inversion_type: str = "tdem" @@ -117,6 +118,7 @@ class TDEMInversionOptions(BaseTDEMOptions, BaseInversionOptions): default_ui_json: ClassVar[Path] = assets_path() / "uijson/tdem_inversion.ui.json" run_command: str = "simpeg_drivers.electromagnetics.time_domain.inversion" title: str = "Time-domain EM (TEM) Inversion" + icon: str = "surveyairborneem" physical_property: str = "conductivity" inversion_type: str = "tdem" diff --git a/simpeg_drivers/electromagnetics/time_domain_1d/options.py b/simpeg_drivers/electromagnetics/time_domain_1d/options.py index 9d8348ef..3ac6abb6 100644 --- a/simpeg_drivers/electromagnetics/time_domain_1d/options.py +++ b/simpeg_drivers/electromagnetics/time_domain_1d/options.py @@ -39,6 +39,7 @@ class TDEM1DForwardOptions(TDEMForwardOptions, Base1DOptions): default_ui_json: ClassVar[Path] = assets_path() / "uijson/tdem1d_forward.ui.json" run_command: str = "simpeg_drivers.electromagnetics.time_domain_1d.forward" title: str = "Time-domain EM-1D (TEM-1D) Forward" + icon: str = "surveyairborneem" inversion_type: str = "tdem 1d" z_channel_bool: bool = True @@ -57,6 +58,7 @@ class TDEM1DInversionOptions(TDEMInversionOptions, Base1DOptions): default_ui_json: ClassVar[Path] = assets_path() / "uijson/tdem1d_inversion.ui.json" run_command: str = "simpeg_drivers.electromagnetics.time_domain_1d.inversion" title: str = "Time-domain EM-1D (TEM-1D) Inversion" + icon: str = "surveyairborneem" inversion_type: str = "tdem 1d" z_channel: PropertyGroup | None = None diff --git a/simpeg_drivers/joint/joint_cross_gradient/options.py b/simpeg_drivers/joint/joint_cross_gradient/options.py index f4f39698..3480ed58 100644 --- a/simpeg_drivers/joint/joint_cross_gradient/options.py +++ b/simpeg_drivers/joint/joint_cross_gradient/options.py @@ -38,6 +38,7 @@ class JointCrossGradientOptions(BaseJointOptions): ) run_command: str = "simpeg_drivers.joint.joint_cross_gradient.driver" title: str = "Joint Cross Gradient Inversion" + icon: str = "function" inversion_type: str = "joint cross gradient" mesh: Octree | None = None diff --git a/simpeg_drivers/joint/joint_petrophysics/options.py b/simpeg_drivers/joint/joint_petrophysics/options.py index 399abc86..2e1a2488 100644 --- a/simpeg_drivers/joint/joint_petrophysics/options.py +++ b/simpeg_drivers/joint/joint_petrophysics/options.py @@ -48,6 +48,7 @@ class JointPetrophysicsOptions(BaseJointOptions): ) run_command: str = "simpeg_drivers.joint.joint_petrophysics.driver" title: str = "Joint Petrophysically Guided Inversion (PGI)" + icon: str = "referencedata" inversion_type: str = "joint petrophysics" group_a_multiplier: float | None = None diff --git a/simpeg_drivers/joint/joint_surveys/options.py b/simpeg_drivers/joint/joint_surveys/options.py index 55c10af9..2896a275 100644 --- a/simpeg_drivers/joint/joint_surveys/options.py +++ b/simpeg_drivers/joint/joint_surveys/options.py @@ -45,6 +45,7 @@ class JointSurveysOptions(BaseJointOptions): ) run_command: str = "simpeg_drivers.joint.joint_surveys.driver" title: str = "Joint Surveys Inversion" + icon: str = "model" inversion_type: str = "joint surveys" models: JointSurveysModelOptions diff --git a/simpeg_drivers/natural_sources/apparent_conductivity/options.py b/simpeg_drivers/natural_sources/apparent_conductivity/options.py index 832f3b67..ad425291 100644 --- a/simpeg_drivers/natural_sources/apparent_conductivity/options.py +++ b/simpeg_drivers/natural_sources/apparent_conductivity/options.py @@ -38,6 +38,7 @@ class AppConForwardOptions(EMDataMixin, BaseForwardOptions): ) run_command: str = "simpeg_drivers.natural_sources.apparent_conductivity.forward" title: str = "Apparent Conductivity Forward" + icon: str = "surveyztem" physical_property: str = "conductivity" inversion_type: str = "apparent conductivity" app_con_channel_bool: bool = True @@ -59,6 +60,7 @@ class AppConInversionOptions(EMDataMixin, BaseInversionOptions): ) run_command: str = "simpeg_drivers.natural_sources.apparent_conductivity.inversion" title: str = "Apparent Conductivity Inversion" + icon: str = "surveyztem" physical_property: str = "conductivity" inversion_type: str = "apparent conductivity" diff --git a/simpeg_drivers/natural_sources/magnetotellurics/options.py b/simpeg_drivers/natural_sources/magnetotellurics/options.py index 60911bb1..3e1d64f0 100644 --- a/simpeg_drivers/natural_sources/magnetotellurics/options.py +++ b/simpeg_drivers/natural_sources/magnetotellurics/options.py @@ -49,6 +49,7 @@ class MTForwardOptions(EMDataMixin, BaseForwardOptions): ) run_command: str = "simpeg_drivers.natural_sources.magnetotellurics.forward" title: str = "Magnetotellurics Forward" + icon: str = "surveymagnetotellurics" physical_property: str = "conductivity" inversion_type: str = "magnetotellurics" @@ -96,6 +97,7 @@ class MTInversionOptions(EMDataMixin, BaseInversionOptions): ) run_command: str = "simpeg_drivers.natural_sources.magnetotellurics.inversion" title: str = "Magnetotellurics Inversion" + icon: str = "surveymagnetotellurics" physical_property: str = "conductivity" inversion_type: str = "magnetotellurics" diff --git a/simpeg_drivers/natural_sources/tipper/options.py b/simpeg_drivers/natural_sources/tipper/options.py index 5196b53c..7600614a 100644 --- a/simpeg_drivers/natural_sources/tipper/options.py +++ b/simpeg_drivers/natural_sources/tipper/options.py @@ -42,6 +42,7 @@ class TipperForwardOptions(EMDataMixin, BaseForwardOptions): default_ui_json: ClassVar[Path] = assets_path() / "uijson/tipper_forward.ui.json" run_command: str = "simpeg_drivers.natural_sources.tipper.forward" title: str = "Tipper Forward" + icon: str = "surveyztem" physical_property: str = "conductivity" inversion_type: str = "tipper" @@ -72,6 +73,7 @@ class TipperInversionOptions(EMDataMixin, BaseInversionOptions): default_ui_json: ClassVar[Path] = assets_path() / "uijson/tipper_inversion.ui.json" run_command: str = "simpeg_drivers.natural_sources.tipper.inversion" title: str = "Tipper Inversion" + icon: str = "surveyztem" physical_property: str = "conductivity" inversion_type: str = "tipper" diff --git a/simpeg_drivers/plate_simulation/match/options.py b/simpeg_drivers/plate_simulation/match/options.py index 90a655ff..e7c7600e 100644 --- a/simpeg_drivers/plate_simulation/match/options.py +++ b/simpeg_drivers/plate_simulation/match/options.py @@ -40,6 +40,7 @@ class PlateMatchOptions(Options): name: ClassVar[str] = "plate_match" default_ui_json: ClassVar[Path] = assets_path() / "uijson/plate_match.ui.json" title: str = "Plate Match" + icon: str = "maxwellplate" run_command: str = "simpeg_drivers.plate_simulation.match.driver" out_group: SimPEGGroup | None = None diff --git a/simpeg_drivers/plate_simulation/options.py b/simpeg_drivers/plate_simulation/options.py index f09b1cde..bdb09a48 100644 --- a/simpeg_drivers/plate_simulation/options.py +++ b/simpeg_drivers/plate_simulation/options.py @@ -126,6 +126,7 @@ class PlateSimulationOptions(Options): name: ClassVar[str] = "plate_simulation" default_ui_json: ClassVar[Path] = assets_path() / "uijson/plate_simulation.ui.json" title: str = "Plate Simulation" + icon: str = "maxwellplate" run_command: str = "simpeg_drivers.plate_simulation.driver" out_group: SimPEGGroup | UIJsonGroup | None = None forward_only: bool = True diff --git a/simpeg_drivers/plate_simulation/sweep/options.py b/simpeg_drivers/plate_simulation/sweep/options.py index 0f2a5295..7e9de095 100644 --- a/simpeg_drivers/plate_simulation/sweep/options.py +++ b/simpeg_drivers/plate_simulation/sweep/options.py @@ -58,6 +58,7 @@ class SweepOptions(Options): name: ClassVar[str] = "plate_sweep" default_ui_json: ClassVar[Path] = assets_path() / "uijson/plate_sweep.ui.json" title: str = "Plate Sweep" + icon: str = "maxwellplate" run_command: str = "simpeg_drivers.plate_simulation.sweep.driver" out_group: SimPEGGroup | None = None forward_only: bool = True diff --git a/simpeg_drivers/potential_fields/gravity/options.py b/simpeg_drivers/potential_fields/gravity/options.py index 763607ce..5333d90c 100644 --- a/simpeg_drivers/potential_fields/gravity/options.py +++ b/simpeg_drivers/potential_fields/gravity/options.py @@ -44,6 +44,7 @@ class GravityForwardOptions(BaseForwardOptions): run_command: str = "simpeg_drivers.potential_fields.gravity.forward" title: str = "Gravity Forward" + icon: str = "surveyairbornegravity" physical_property: str = "density" inversion_type: str = "gravity" @@ -88,6 +89,7 @@ class GravityInversionOptions(BaseInversionOptions): run_command: str = "simpeg_drivers.potential_fields.gravity.inversion" title: str = "Gravity Inversion" + icon: str = "surveyairbornegravity" physical_property: str = "density" inversion_type: str = "gravity" diff --git a/simpeg_drivers/potential_fields/magnetic_scalar/options.py b/simpeg_drivers/potential_fields/magnetic_scalar/options.py index 1e1839e7..7f422791 100644 --- a/simpeg_drivers/potential_fields/magnetic_scalar/options.py +++ b/simpeg_drivers/potential_fields/magnetic_scalar/options.py @@ -56,6 +56,7 @@ class MagneticForwardOptions(BaseForwardOptions): run_command: str = "simpeg_drivers.potential_fields.magnetic_scalar.forward" title: str = "Magnetic Scalar Forward" + icon: str = "surveyairbornemagnetics" physical_property: str = "susceptibility" inversion_type: str = "magnetic scalar" @@ -112,6 +113,7 @@ class MagneticInversionOptions(BaseInversionOptions): run_command: str = "simpeg_drivers.potential_fields.magnetic_scalar.inversion" title: str = "Magnetic Scalar Inversion" + icon: str = "surveyairbornemagnetics" physical_property: str = "susceptibility" inversion_type: str = "magnetic scalar" diff --git a/simpeg_drivers/potential_fields/magnetic_vector/options.py b/simpeg_drivers/potential_fields/magnetic_vector/options.py index 58e72ae9..70ab0bfe 100644 --- a/simpeg_drivers/potential_fields/magnetic_vector/options.py +++ b/simpeg_drivers/potential_fields/magnetic_vector/options.py @@ -60,6 +60,7 @@ class MagneticVectorForwardOptions(BaseForwardOptions): ) run_command: str = "simpeg_drivers.potential_fields.magnetic_vector.forward" title: str = "Magnetic Vector Forward" + icon: str = "surveyairbornemagnetics" physical_property: str = "susceptibility" inversion_type: str = "magnetic vector" @@ -114,6 +115,7 @@ class MagneticVectorInversionOptions(BaseInversionOptions): ) run_command: str = "simpeg_drivers.potential_fields.magnetic_vector.inversion" title: str = "Magnetic Vector Inversion" + icon: str = "surveyairbornemagnetics" physical_property: str = "susceptibility" inversion_type: str = "magnetic vector" diff --git a/simpeg_drivers/utils/tile_estimate.py b/simpeg_drivers/utils/tile_estimate.py index 18ef5296..f68ec2a4 100644 --- a/simpeg_drivers/utils/tile_estimate.py +++ b/simpeg_drivers/utils/tile_estimate.py @@ -41,6 +41,7 @@ class TileParameters(Options): """ default_ui_json: ClassVar[Path] = assets_path() / "uijson/tile_estimator.ui.json" + icon: str = "tilelist" simulation: SimPEGGroup render_plot: bool = True From 07037e87326076d9584e0664e7a1097c21d5ef01 Mon Sep 17 00:00:00 2001 From: domfournier Date: Fri, 24 Apr 2026 10:53:49 -0700 Subject: [PATCH 03/24] MOdify test to hit issue --- tests/run_tests/driver_joint_cross_gradient_test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/run_tests/driver_joint_cross_gradient_test.py b/tests/run_tests/driver_joint_cross_gradient_test.py index 4b33ab32..e1f57a00 100644 --- a/tests/run_tests/driver_joint_cross_gradient_test.py +++ b/tests/run_tests/driver_joint_cross_gradient_test.py @@ -233,6 +233,7 @@ def test_joint_cross_gradient_inv_run( starting_model=0.0, reference_model=0.0, upper_bound=1.0, + x_norm=1.1, tile_spatial=2, auto_scale_tiles=True, chi_factor=0.8, @@ -299,7 +300,9 @@ def test_joint_cross_gradient_inv_run( percentile=100, ) - driver = JointCrossGradientDriver(joint_params) + file = joint_params.write_ui_json(path=tmp_path / "Joint_Inv_run.ui.json") + + driver = JointCrossGradientDriver.start(file) # Check that chi factors set on the sub drivers are preserved forward np.testing.assert_allclose( From 14787774309e6e4dc813dcc2b2d34877ec15bf49 Mon Sep 17 00:00:00 2001 From: domfournier Date: Fri, 24 Apr 2026 12:21:49 -0700 Subject: [PATCH 04/24] Over the start method. Start using UIJson class --- simpeg_drivers/driver.py | 39 ++++++++++++++++++++++++++++++++++- simpeg_drivers/utils/utils.py | 16 +++++++------- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/simpeg_drivers/driver.py b/simpeg_drivers/driver.py index 91e3df00..e9d6cd54 100644 --- a/simpeg_drivers/driver.py +++ b/simpeg_drivers/driver.py @@ -14,7 +14,7 @@ from __future__ import annotations from abc import abstractmethod, ABC - +from typing import Self from copy import deepcopy import sys from datetime import datetime, timedelta @@ -29,9 +29,11 @@ from geoapps_utils.run import load_ui_json_as_dict from geoapps_utils.utils.importing import GeoAppsError +from geoh5py import Workspace from geoh5py.groups import SimPEGGroup from geoh5py.objects import FEMSurvey from geoh5py.shared.utils import fetch_active_workspace +from geoh5py.ui_json import BaseUIJson from simpeg import ( directives, @@ -489,6 +491,41 @@ def run(self): with fetch_active_workspace(self.workspace, mode="r+"): self.directives.save_iteration_log_files.write(1) + @classmethod + def start(cls, filepath: str | Path | BaseUIJson, mode="r+", **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 kwargs: Additional keyword arguments for Options class. + + :return: Self object. + """ + + uijson = ( + BaseUIJson.read(filepath) if isinstance(filepath, str | Path) else filepath + ) + + if not isinstance(uijson, BaseUIJson): + raise TypeError("Input file must be a string path or an InputFile object.") + + if uijson.geoh5 is None: + raise GeoAppsError("The application needs a valid 'geoh5' file.") + + with Workspace(uijson.geoh5, mode=mode) as workspace: + try: + data = uijson.to_params(workspace) + kwargs.update(data) + params = cls._params_class.build(workspace=workspace, **kwargs) + driver = cls(params) + driver.run() + except GeoAppsError as error: + sys.exit(1) + + return driver + @classmethod def start_dask_run( cls, json_path: Path, n_workers: int | None = None, n_threads: int | None = None diff --git a/simpeg_drivers/utils/utils.py b/simpeg_drivers/utils/utils.py index 7b8810d5..0566dfa6 100644 --- a/simpeg_drivers/utils/utils.py +++ b/simpeg_drivers/utils/utils.py @@ -40,7 +40,7 @@ from geoh5py.objects.surveys.electromagnetics.base import LargeLoopGroundEMSurvey from geoh5py.shared import INTEGER_NDV from geoh5py.shared.utils import fetch_active_workspace, mask_by_extent, stringify -from geoh5py.ui_json import InputFile +from geoh5py.ui_json import BaseUIJson from grid_apps.utils import octree_2_treemesh from scipy.interpolate import interp1d from scipy.spatial import ConvexHull, cKDTree @@ -608,14 +608,14 @@ def simpeg_group_to_driver(group: SimPEGGroup, workspace: Workspace) -> Driver: :returns: InversionDriver object. """ + ui_json_dict = deepcopy(group.options) + ui_json_dict["geoh5"] = workspace + uijson = BaseUIJson.from_dict(ui_json_dict) + data = uijson.to_params(workspace) + data["out_group"] = group - ui_json = deepcopy(group.options) - ui_json["geoh5"] = workspace - - ifile = InputFile(ui_json=ui_json) - inversion_driver = driver_class_from_dict(ifile.ui_json) - ifile.set_data_value("out_group", group) - params = inversion_driver._params_class.build(ifile) # pylint: disable=protected-access + inversion_driver = driver_class_from_dict(ui_json_dict) + params = inversion_driver._params_class.build(**data) # pylint: disable=protected-access return inversion_driver(params) From e2bb6f4f42468390c80eadd367739fd954f7563d Mon Sep 17 00:00:00 2001 From: domfournier Date: Fri, 24 Apr 2026 12:23:32 -0700 Subject: [PATCH 05/24] Update test to check values --- tests/run_tests/driver_joint_cross_gradient_test.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/run_tests/driver_joint_cross_gradient_test.py b/tests/run_tests/driver_joint_cross_gradient_test.py index 4b33ab32..76b6f614 100644 --- a/tests/run_tests/driver_joint_cross_gradient_test.py +++ b/tests/run_tests/driver_joint_cross_gradient_test.py @@ -234,6 +234,7 @@ def test_joint_cross_gradient_inv_run( reference_model=0.0, upper_bound=1.0, tile_spatial=2, + x_norm=1.1, auto_scale_tiles=True, chi_factor=0.8, ) @@ -298,8 +299,8 @@ def test_joint_cross_gradient_inv_run( cross_gradient_weight_c_b=1e0, percentile=100, ) - - driver = JointCrossGradientDriver(joint_params) + file = joint_params.write_ui_json(tmp_path / "Joint_Inv_run.ui.json") + driver = JointCrossGradientDriver.start(file) # Check that chi factors set on the sub drivers are preserved forward np.testing.assert_allclose( From 07b9f5a1a9e46dad800f3e0888890fce6f51fa3d Mon Sep 17 00:00:00 2001 From: domfournier Date: Fri, 24 Apr 2026 15:10:59 -0700 Subject: [PATCH 06/24] Augment test --- tests/run_tests/driver_joint_cross_gradient_test.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/run_tests/driver_joint_cross_gradient_test.py b/tests/run_tests/driver_joint_cross_gradient_test.py index 76b6f614..bde003b5 100644 --- a/tests/run_tests/driver_joint_cross_gradient_test.py +++ b/tests/run_tests/driver_joint_cross_gradient_test.py @@ -15,6 +15,7 @@ from geoh5py.groups import GroupTypeEnum, PropertyGroup from geoh5py.objects import CurrentElectrode, Octree, Points from geoh5py.workspace import Workspace +from simpeg.directives import UpdateIRLS from simpeg_drivers.electricals.direct_current.three_dimensions import ( DC3DForwardDriver, @@ -301,13 +302,15 @@ def test_joint_cross_gradient_inv_run( ) file = joint_params.write_ui_json(tmp_path / "Joint_Inv_run.ui.json") driver = JointCrossGradientDriver.start(file) + driver.run() - # Check that chi factors set on the sub drivers are preserved forward - np.testing.assert_allclose( - driver.data_misfit.multipliers, [0.8, 0.8, 1.0, 1.0, 1.0], atol=1e-3 + # Check that the norm applied to the sub-driver is maintained + irls_directive = next( + directive + for directive in driver.directives + if isinstance(directive, UpdateIRLS) ) - - driver.run() + np.testing.assert_almost_equal(irls_directive.metrics.input_norms[0][1], 1.1) if not pytest: return From 85c142475c517d5bc0fe5f0e2f8ee2f250b2b29b Mon Sep 17 00:00:00 2001 From: domfournier Date: Fri, 24 Apr 2026 15:29:50 -0700 Subject: [PATCH 07/24] Remove unrecognized uuids --- simpeg_drivers-assets/uijson/magnetic_vector_forward.ui.json | 3 +-- .../uijson/magnetic_vector_pde_forward.ui.json | 3 +-- .../uijson/magnetic_vector_pde_inversion.ui.json | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/simpeg_drivers-assets/uijson/magnetic_vector_forward.ui.json b/simpeg_drivers-assets/uijson/magnetic_vector_forward.ui.json index c2f2d31d..2ca14bad 100644 --- a/simpeg_drivers-assets/uijson/magnetic_vector_forward.ui.json +++ b/simpeg_drivers-assets/uijson/magnetic_vector_forward.ui.json @@ -99,8 +99,7 @@ "{f26feba3-aded-494b-b9e9-b2bbcbe298e1}", "{48f5054a-1c5c-4ca4-9048-80f36dc60a06}", "{b020a277-90e2-4cd7-84d6-612ee3f25051}", - "{4b99204c-d133-4579-a916-a9c8b98cfccb}", - "{028e4905-cc97-4dab-b1bf-d76f58b501b5}" + "{4b99204c-d133-4579-a916-a9c8b98cfccb}" ], "value": "" }, diff --git a/simpeg_drivers-assets/uijson/magnetic_vector_pde_forward.ui.json b/simpeg_drivers-assets/uijson/magnetic_vector_pde_forward.ui.json index 1405802b..b7279be4 100644 --- a/simpeg_drivers-assets/uijson/magnetic_vector_pde_forward.ui.json +++ b/simpeg_drivers-assets/uijson/magnetic_vector_pde_forward.ui.json @@ -98,8 +98,7 @@ "{f26feba3-aded-494b-b9e9-b2bbcbe298e1}", "{48f5054a-1c5c-4ca4-9048-80f36dc60a06}", "{b020a277-90e2-4cd7-84d6-612ee3f25051}", - "{4b99204c-d133-4579-a916-a9c8b98cfccb}", - "{028e4905-cc97-4dab-b1bf-d76f58b501b5}" + "{4b99204c-d133-4579-a916-a9c8b98cfccb}" ], "value": "" }, diff --git a/simpeg_drivers-assets/uijson/magnetic_vector_pde_inversion.ui.json b/simpeg_drivers-assets/uijson/magnetic_vector_pde_inversion.ui.json index 8ef6fbb8..242b64ea 100644 --- a/simpeg_drivers-assets/uijson/magnetic_vector_pde_inversion.ui.json +++ b/simpeg_drivers-assets/uijson/magnetic_vector_pde_inversion.ui.json @@ -47,8 +47,7 @@ "{f26feba3-aded-494b-b9e9-b2bbcbe298e1}", "{48f5054a-1c5c-4ca4-9048-80f36dc60a06}", "{b020a277-90e2-4cd7-84d6-612ee3f25051}", - "{4b99204c-d133-4579-a916-a9c8b98cfccb}", - "{028e4905-cc97-4dab-b1bf-d76f58b501b5}" + "{4b99204c-d133-4579-a916-a9c8b98cfccb}" ], "value": "" }, From b397dcf510a7cae363d4cae0d3af16f817d57901 Mon Sep 17 00:00:00 2001 From: domfournier Date: Fri, 24 Apr 2026 15:31:21 -0700 Subject: [PATCH 08/24] Issue issues in mvi pde uijson --- .../uijson/magnetic_vector_pde_forward.ui.json | 4 +--- .../uijson/magnetic_vector_pde_inversion.ui.json | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/simpeg_drivers-assets/uijson/magnetic_vector_pde_forward.ui.json b/simpeg_drivers-assets/uijson/magnetic_vector_pde_forward.ui.json index b7279be4..866970fe 100644 --- a/simpeg_drivers-assets/uijson/magnetic_vector_pde_forward.ui.json +++ b/simpeg_drivers-assets/uijson/magnetic_vector_pde_forward.ui.json @@ -195,13 +195,11 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", "value": 1, "min": 1, "max": 1000, "verbose": 2, + "visible": false, "tooltip": "Splits the objective function into spatial tiles for distributed computation using the Dask library" }, "max_chunk_size": { diff --git a/simpeg_drivers-assets/uijson/magnetic_vector_pde_inversion.ui.json b/simpeg_drivers-assets/uijson/magnetic_vector_pde_inversion.ui.json index 242b64ea..ea39ca71 100644 --- a/simpeg_drivers-assets/uijson/magnetic_vector_pde_inversion.ui.json +++ b/simpeg_drivers-assets/uijson/magnetic_vector_pde_inversion.ui.json @@ -626,13 +626,11 @@ "tile_spatial": { "group": "Compute", "label": "Number of tiles", - "parent": "data_object", - "isValue": true, - "property": "", "value": 1, "min": 1, "max": 1000, "verbose": 2, + "visible": false, "tooltip": "Splits the objective function into spatial tiles for distributed computation using the Dask library" }, "out_group": { From 623d2757f76b0a8add250109c6d96dc1c53dd4f5 Mon Sep 17 00:00:00 2001 From: domfournier Date: Sat, 25 Apr 2026 11:26:38 -0700 Subject: [PATCH 09/24] Fix more uijson --- simpeg_drivers-assets/uijson/tdem1d_forward.ui.json | 2 +- simpeg_drivers-assets/uijson/tdem1d_inversion.ui.json | 2 +- simpeg_drivers-assets/uijson/tdem_forward.ui.json | 2 +- simpeg_drivers-assets/uijson/tdem_inversion.ui.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/simpeg_drivers-assets/uijson/tdem1d_forward.ui.json b/simpeg_drivers-assets/uijson/tdem1d_forward.ui.json index 37e6610e..c90c5e6d 100644 --- a/simpeg_drivers-assets/uijson/tdem1d_forward.ui.json +++ b/simpeg_drivers-assets/uijson/tdem1d_forward.ui.json @@ -34,7 +34,7 @@ "
Wire
Current * number of turns (NI).
", "" ], - "value": "dB/dt (T/s)" + "value": "Airborne dB/dt (V/Am^4)" }, "z_channel_bool": { "group": "Survey", diff --git a/simpeg_drivers-assets/uijson/tdem1d_inversion.ui.json b/simpeg_drivers-assets/uijson/tdem1d_inversion.ui.json index 8b96d7d3..694e9c6b 100644 --- a/simpeg_drivers-assets/uijson/tdem1d_inversion.ui.json +++ b/simpeg_drivers-assets/uijson/tdem1d_inversion.ui.json @@ -34,7 +34,7 @@ "
Wire
Current * number of turns (NI).
", "" ], - "value": "dB/dt (T/s)" + "value": "Airborne dB/dt (V/Am^4)" }, "z_channel": { "association": [ diff --git a/simpeg_drivers-assets/uijson/tdem_forward.ui.json b/simpeg_drivers-assets/uijson/tdem_forward.ui.json index 2a2813a2..dbc2d85f 100644 --- a/simpeg_drivers-assets/uijson/tdem_forward.ui.json +++ b/simpeg_drivers-assets/uijson/tdem_forward.ui.json @@ -56,7 +56,7 @@ "
Wire
Current * number of turns (NI).
", "" ], - "value": "dB/dt (T/s)" + "value": "Airborne dB/dt (V/Am^4)" }, "vertical_channel_bool": { "group": "Survey", diff --git a/simpeg_drivers-assets/uijson/tdem_inversion.ui.json b/simpeg_drivers-assets/uijson/tdem_inversion.ui.json index a1be8f08..340a04d3 100644 --- a/simpeg_drivers-assets/uijson/tdem_inversion.ui.json +++ b/simpeg_drivers-assets/uijson/tdem_inversion.ui.json @@ -56,7 +56,7 @@ "
Wire
Current * number of turns (NI).
", "" ], - "value": "dB/dt (T/s)" + "value": "Airborne dB/dt (V/Am^4)" }, "vertical_channel": { "association": [ From 28e65382009b5f8f951c7b791034e8caba423430 Mon Sep 17 00:00:00 2001 From: domfournier Date: Sat, 25 Apr 2026 11:27:16 -0700 Subject: [PATCH 10/24] Add temporary implementation of Options.write_ui_json with UIJson --- simpeg_drivers/options.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/simpeg_drivers/options.py b/simpeg_drivers/options.py index d1b35a10..19c5e2fa 100644 --- a/simpeg_drivers/options.py +++ b/simpeg_drivers/options.py @@ -18,6 +18,7 @@ import numpy as np from geoapps_utils.base import Options +from geoapps_utils.utils.formatters import recursive_flatten from geoh5py.data import ( BooleanData, DataAssociationEnum, @@ -29,7 +30,7 @@ from geoh5py.groups import PropertyGroup, SimPEGGroup, UIJsonGroup from geoh5py.objects import DrapeModel, Grid2D, Octree, Points from geoh5py.objects.surveys.electromagnetics.base import BaseEMSurvey -from geoh5py.ui_json import InputFile +from geoh5py.ui_json import BaseUIJson, InputFile from pydantic import ( AliasChoices, BaseModel, @@ -240,6 +241,19 @@ def _create_input_file_from_attributes(self) -> InputFile: ifile.set_data_value("version", public_version()) return ifile + 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 = BaseUIJson.read(self.default_ui_json) + flatten = recursive_flatten(self.model_dump(exclude_unset=True)) + ui_json.set_values(**flatten) + + return ui_json.write(path) + class ModelOptions(BaseModel): """ From 5a5aa909a37ccb34a9dbfb81b19c52c5a9afd811 Mon Sep 17 00:00:00 2001 From: domfournier Date: Sat, 25 Apr 2026 11:28:20 -0700 Subject: [PATCH 11/24] Special models for 2D without y models --- simpeg_drivers/electricals/base_2d.py | 20 ++++++++++++++++++- .../direct_current/two_dimensions/options.py | 12 ++++------- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/simpeg_drivers/electricals/base_2d.py b/simpeg_drivers/electricals/base_2d.py index 3f0cf079..42242f90 100644 --- a/simpeg_drivers/electricals/base_2d.py +++ b/simpeg_drivers/electricals/base_2d.py @@ -14,9 +14,10 @@ from logging import getLogger import numpy as np +from geoh5py.data import FloatData from geoh5py.objects import DrapeModel, Octree, PotentialElectrode from geoh5py.ui_json.ui_json import fetch_active_workspace -from pydantic import field_validator, model_validator +from pydantic import AliasChoices, Field, field_validator, model_validator from simpeg_drivers.components.meshes import InversionMesh from simpeg_drivers.driver import BaseDriver @@ -24,6 +25,8 @@ CoreOptions, DrapeModelOptions, LineSelectionOptions, + ModelOptions, + ModelTypeEnum, ) from simpeg_drivers.utils.surveys import ( create_mesh_by_line_id, @@ -34,6 +37,21 @@ logger = getLogger(__name__) +class Conductivity2DModelOptions(ModelOptions): + """ + Options for the conductivity model used in all of EM methods. + """ + + model_type: ModelTypeEnum = ModelTypeEnum.conductivity + conductivity_model: float | FloatData | None = Field( + None, + validation_alias=AliasChoices("background_conductivity", "conductivity_model"), + ) + + length_scale_y: None = None + y_norm: None = None + + class Base2DOptions(CoreOptions): """ Base options for the Direct Current 2D forward and inverse driver. diff --git a/simpeg_drivers/electricals/direct_current/two_dimensions/options.py b/simpeg_drivers/electricals/direct_current/two_dimensions/options.py index 8b850438..5c2cf09c 100644 --- a/simpeg_drivers/electricals/direct_current/two_dimensions/options.py +++ b/simpeg_drivers/electricals/direct_current/two_dimensions/options.py @@ -17,12 +17,8 @@ from geoh5py.data import FloatData from simpeg_drivers import assets_path -from simpeg_drivers.electricals.base_2d import Base2DOptions -from simpeg_drivers.options import ( - BaseForwardOptions, - BaseInversionOptions, - ConductivityModelOptions, -) +from simpeg_drivers.electricals.base_2d import Base2DOptions, Conductivity2DModelOptions +from simpeg_drivers.options import BaseForwardOptions, BaseInversionOptions class DC2DForwardOptions(BaseForwardOptions, Base2DOptions): @@ -43,7 +39,7 @@ class DC2DForwardOptions(BaseForwardOptions, Base2DOptions): inversion_type: str = "direct current 2d" potential_channel_bool: bool = True - models: ConductivityModelOptions + models: Conductivity2DModelOptions class DC2DInversionOptions(BaseInversionOptions, Base2DOptions): @@ -67,4 +63,4 @@ class DC2DInversionOptions(BaseInversionOptions, Base2DOptions): potential_channel: FloatData potential_uncertainty: float | FloatData | None = None - models: ConductivityModelOptions + models: Conductivity2DModelOptions From 49edce4cbd1b14d668b547b9c4d056a76ff2c5a2 Mon Sep 17 00:00:00 2001 From: domfournier Date: Sat, 25 Apr 2026 11:28:42 -0700 Subject: [PATCH 12/24] Fix joint cross test --- tests/run_tests/driver_joint_cross_gradient_test.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/run_tests/driver_joint_cross_gradient_test.py b/tests/run_tests/driver_joint_cross_gradient_test.py index bde003b5..3c0bde34 100644 --- a/tests/run_tests/driver_joint_cross_gradient_test.py +++ b/tests/run_tests/driver_joint_cross_gradient_test.py @@ -302,12 +302,11 @@ def test_joint_cross_gradient_inv_run( ) file = joint_params.write_ui_json(tmp_path / "Joint_Inv_run.ui.json") driver = JointCrossGradientDriver.start(file) - driver.run() # Check that the norm applied to the sub-driver is maintained irls_directive = next( directive - for directive in driver.directives + for directive in driver.directives.directive_list if isinstance(directive, UpdateIRLS) ) np.testing.assert_almost_equal(irls_directive.metrics.input_norms[0][1], 1.1) From bb202ba79661099117e8028c288078184d80a43f Mon Sep 17 00:00:00 2001 From: domfournier Date: Sat, 25 Apr 2026 12:06:41 -0700 Subject: [PATCH 13/24] Fix sens_wts_threshold to keep test target --- tests/run_tests/driver_grav_test.py | 1 + tests/run_tests/driver_joint_cross_gradient_test.py | 1 + tests/run_tests/driver_rotated_gradients_test.py | 1 + 3 files changed, 3 insertions(+) diff --git a/tests/run_tests/driver_grav_test.py b/tests/run_tests/driver_grav_test.py index 41e5400b..cb06f566 100644 --- a/tests/run_tests/driver_grav_test.py +++ b/tests/run_tests/driver_grav_test.py @@ -133,6 +133,7 @@ def test_gravity_run( starting_model=1e-4, topography_object=components.topography, reference_model=0.0, + sens_wts_threshold=1.0, save_sensitivities=True, ) params.write_ui_json(path=tmp_path / "Inv_run.ui.json") diff --git a/tests/run_tests/driver_joint_cross_gradient_test.py b/tests/run_tests/driver_joint_cross_gradient_test.py index 3c0bde34..3fbc085e 100644 --- a/tests/run_tests/driver_joint_cross_gradient_test.py +++ b/tests/run_tests/driver_joint_cross_gradient_test.py @@ -298,6 +298,7 @@ def test_joint_cross_gradient_inv_run( cross_gradient_weight_a_b=1e0, cross_gradient_weight_c_a=1e0, cross_gradient_weight_c_b=1e0, + sens_wts_threshold=1.0, percentile=100, ) file = joint_params.write_ui_json(tmp_path / "Joint_Inv_run.ui.json") diff --git a/tests/run_tests/driver_rotated_gradients_test.py b/tests/run_tests/driver_rotated_gradients_test.py index 0b47de3e..dca72fa6 100644 --- a/tests/run_tests/driver_rotated_gradients_test.py +++ b/tests/run_tests/driver_rotated_gradients_test.py @@ -151,6 +151,7 @@ def test_rotated_grad_run( max_global_iterations=max_iterations, initial_beta_ratio=1e-1, percentile=95, + sens_wts_threshold=1.0, save_sensitivities=True, ) params.write_ui_json(path=tmp_path / "Inv_run.ui.json") From 77a40c7cc8c44fc0fb448dc2d28487270ef534c2 Mon Sep 17 00:00:00 2001 From: domfournier Date: Sat, 25 Apr 2026 12:16:09 -0700 Subject: [PATCH 14/24] Remove InputFile from plate_match --- .../plate_simulation/match/driver.py | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/simpeg_drivers/plate_simulation/match/driver.py b/simpeg_drivers/plate_simulation/match/driver.py index 467fd50d..3e61daa4 100644 --- a/simpeg_drivers/plate_simulation/match/driver.py +++ b/simpeg_drivers/plate_simulation/match/driver.py @@ -33,7 +33,7 @@ from geoh5py.groups import PropertyGroup, SimPEGGroup from geoh5py.objects import AirborneTEMReceivers, MaxwellPlate, Surface from geoh5py.objects.maxwell_plate import PlateGeometry -from geoh5py.ui_json import InputFile +from geoh5py.ui_json import BaseUIJson from scipy import ndimage, signal from scipy.sparse import csr_matrix from scipy.spatial import cKDTree @@ -166,17 +166,16 @@ def start(cls, filepath: str | Path, mode="r+", **_) -> Self: filepath = Path(filepath).resolve() # TODO: Replace with UIJson when fully implemented - # uijson = PlateMatchUIJson.read(filepath) - uijson = InputFile.read_ui_json(filepath) + uijson = BaseUIJson.read(filepath) with uijson.geoh5.open(mode=mode): try: - options = PlateMatchOptions.build(uijson) + data = uijson.to_params(uijson.geoh5) logger.info("Initializing application . . .") - driver = cls(options) + driver = cls(**data) logger.info("Running application . . .") driver.run() - logger.info("Results saved to %s", options.geoh5.h5file) + logger.info("Results saved to %s", uijson.geoh5.h5file) except GeoAppsError as error: logger.warning("\n\nApplicationError: %s\n\n", error) @@ -385,14 +384,13 @@ def run(self): with Workspace(self.params.simulation_files[best], mode="r") as ws: survey = fetch_survey(ws) - ui_json = survey.parent.parent.options - - ui_json["geoh5"] = ws - ifile = InputFile(ui_json=ui_json) + ui_json_dict = survey.parent.parent.options + ui_json_dict["geoh5"] = ws + uijson = BaseUIJson.from_dict(ui_json_dict) # Avoid getting pydantic deprecation warnings from old PlateSimulations stored with suppress_logging(): - options = PlateSimulationOptions.build(ifile) + options = PlateSimulationOptions.build(**uijson.to_params(ws)) dir_correction = strike_angle[ii] + 180 if flip else strike_angle[ii] ind_center = int(centers[best]) From ab80337c0fc4cea300b68392c5ffcb833dee90e0 Mon Sep 17 00:00:00 2001 From: domfournier Date: Sun, 26 Apr 2026 10:14:21 -0700 Subject: [PATCH 15/24] Continue removing InputFile --- .../uijson/plate_sweep.ui.json | 1 - simpeg_drivers/driver.py | 2 +- simpeg_drivers/options.py | 7 +- simpeg_drivers/plate_simulation/options.py | 18 ++-- .../plate_simulation/sweep/options.py | 54 ++++++---- tests/plate_simulation/runtest/sweep_test.py | 100 +++++++++--------- 6 files changed, 92 insertions(+), 90 deletions(-) diff --git a/simpeg_drivers-assets/uijson/plate_sweep.ui.json b/simpeg_drivers-assets/uijson/plate_sweep.ui.json index ffe4c361..b85dffa0 100644 --- a/simpeg_drivers-assets/uijson/plate_sweep.ui.json +++ b/simpeg_drivers-assets/uijson/plate_sweep.ui.json @@ -23,7 +23,6 @@ "main": true, "group": "Base", "label": "Output directory", - "directoryOnly": true, "enabled": false, "optional": true, "tootip": "Directory to store simulation results, relative to the working geoh5.", diff --git a/simpeg_drivers/driver.py b/simpeg_drivers/driver.py index e9d6cd54..120625a3 100644 --- a/simpeg_drivers/driver.py +++ b/simpeg_drivers/driver.py @@ -509,7 +509,7 @@ def start(cls, filepath: str | Path | BaseUIJson, mode="r+", **kwargs) -> Self: ) if not isinstance(uijson, BaseUIJson): - raise TypeError("Input file must be a string path or an InputFile object.") + raise TypeError("Input file must be a string path or an BaseUIJson object.") if uijson.geoh5 is None: raise GeoAppsError("The application needs a valid 'geoh5' file.") diff --git a/simpeg_drivers/options.py b/simpeg_drivers/options.py index 19c5e2fa..f29960f5 100644 --- a/simpeg_drivers/options.py +++ b/simpeg_drivers/options.py @@ -30,7 +30,7 @@ from geoh5py.groups import PropertyGroup, SimPEGGroup, UIJsonGroup from geoh5py.objects import DrapeModel, Grid2D, Octree, Points from geoh5py.objects.surveys.electromagnetics.base import BaseEMSurvey -from geoh5py.ui_json import BaseUIJson, InputFile +from geoh5py.ui_json import BaseUIJson from pydantic import ( AliasChoices, BaseModel, @@ -236,11 +236,6 @@ def padding_cells(self) -> int: return 4 if self.inversion_type in ["fdem", "tdem"] else 6 - def _create_input_file_from_attributes(self) -> InputFile: - ifile = super()._create_input_file_from_attributes() - ifile.set_data_value("version", public_version()) - return ifile - def write_ui_json(self, path: Path) -> Path: """ Write UI JSON file. diff --git a/simpeg_drivers/plate_simulation/options.py b/simpeg_drivers/plate_simulation/options.py index 7b156499..3148ceb5 100644 --- a/simpeg_drivers/plate_simulation/options.py +++ b/simpeg_drivers/plate_simulation/options.py @@ -14,7 +14,8 @@ from geoapps_utils.base import Options from geoh5py.groups import SimPEGGroup, UIJsonGroup -from geoh5py.ui_json import InputFile +from geoh5py.shared.utils import fetch_active_workspace +from geoh5py.ui_json import BaseUIJson from simpeg_drivers import assets_path from simpeg_drivers.options import BaseForwardOptions @@ -59,15 +60,10 @@ def simulation_parameters(self) -> BaseForwardOptions: simulation_options = deepcopy(self.simulation.options) simulation_options["geoh5"] = self.geoh5 - input_file = InputFile(ui_json=simulation_options, validate=False) - if input_file.ui_json is None: - raise ValueError("Input file must have ui_json set.") + ui_json = BaseUIJson.from_dict(simulation_options) - input_file.ui_json["mesh"]["value"] = None + with fetch_active_workspace(self.geoh5) as workspace: + data = ui_json.to_params(workspace=workspace, validate=False) + driver = driver_class_from_dict(data) - if input_file.data is None: - raise ValueError("Input file data must be set.") - - driver = driver_class_from_dict(input_file.data) - - return driver._params_class.build(input_file.data) # pylint: disable=protected-access + return driver._params_class.build(data) # pylint: disable=protected-access diff --git a/simpeg_drivers/plate_simulation/sweep/options.py b/simpeg_drivers/plate_simulation/sweep/options.py index ac043f3b..017b93c5 100644 --- a/simpeg_drivers/plate_simulation/sweep/options.py +++ b/simpeg_drivers/plate_simulation/sweep/options.py @@ -14,9 +14,10 @@ import numpy as np from geoapps_utils.base import Options +from geoh5py import Workspace from geoh5py.groups import SimPEGGroup, UIJsonGroup -from geoh5py.shared.utils import stringify -from geoh5py.ui_json import InputFile +from geoh5py.shared.utils import fetch_active_workspace, stringify +from geoh5py.ui_json import BaseUIJson from pydantic import BaseModel, ConfigDict, field_serializer, field_validator from simpeg_drivers import assets_path @@ -143,26 +144,34 @@ def trials(self) -> list[dict]: return trials @staticmethod - def all_hashable_options(options: dict) -> dict: - """Recurses through UIJson options to return flat dictionary of all key/values.""" - - # TODO: Use the base UIJson to read options and flatten instead of - # InputFile. Requires GEOPY-1875. + def all_hashable_options(options: dict, workspace: Workspace) -> dict: + """ + Recurses through UIJson options to return flat dictionary of all key/values. - ifile = InputFile(ui_json=options, validate=False) - exceptions = list(Options.model_fields) + ["version", "icon", "documentation"] - # TODO: add these to the Options fields with empty string defaults. - out = {} - for k, v in ifile.data.items(): - if k in exceptions: - continue + :param options: Options dictionary + :param workspace: Workspace to fetch objects from. + """ + ifile = BaseUIJson.from_dict(options) + + with fetch_active_workspace(workspace, mode="r") as ws: + data = ifile.to_params(workspace=ws, validate=False) + exceptions = list(Options.model_fields) + [ + "version", + "icon", + "documentation", + ] + # TODO: add these to the Options fields with empty string defaults. + out = {} + for k, v in data.items(): + if k in exceptions: + continue - if isinstance(v, SimPEGGroup | UIJsonGroup): - opts = v.options - opts["geoh5"] = options["geoh5"] - out.update(SweepOptions.all_hashable_options(opts)) - else: - out[k] = v + if isinstance(v, SimPEGGroup | UIJsonGroup): + opts = v.options + opts["geoh5"] = ws + out.update(SweepOptions.all_hashable_options(opts, ws)) + else: + out[k] = v return out @@ -171,4 +180,7 @@ def template_options(self): """Return a flat version of the template.options dictionary.""" options = self.template.options options["geoh5"] = self.geoh5 - return stringify(SweepOptions.all_hashable_options(options)) + + with fetch_active_workspace(self.geoh5, mode="r") as workspace: + template = SweepOptions.all_hashable_options(options, workspace) + return stringify(template) diff --git a/tests/plate_simulation/runtest/sweep_test.py b/tests/plate_simulation/runtest/sweep_test.py index 4484b6ae..58e52c6a 100644 --- a/tests/plate_simulation/runtest/sweep_test.py +++ b/tests/plate_simulation/runtest/sweep_test.py @@ -10,7 +10,7 @@ from geoh5py import Workspace from geoh5py.groups import SimPEGGroup -from geoh5py.ui_json import InputFile +from geoh5py.ui_json import BaseUIJson from pandas import read_excel from simpeg_drivers import assets_path @@ -27,38 +27,38 @@ def setup_plate_sweep(workspace) -> SimPEGGroup: data = get_survey(workspace, method="gravity", options=options.survey) topo = get_topography_surface(workspace, options) - gravity = SimPEGGroup.create(workspace, name="gravity fwd") options = GravityForwardOptions.model_construct() - fwr_ifile = InputFile.read_ui_json(options.default_ui_json) - options_dict = fwr_ifile.ui_json - options_dict["inversion_type"] = "gravity" - options_dict["forward_only"] = True - options_dict["geoh5"] = str(workspace.h5file) - options_dict["topography_object"]["value"] = str(topo.uid) - options_dict["data_object"]["value"] = str(data.uid) - options_dict["out_group"]["value"] = str(gravity.uid) - gravity.options = options_dict - - simulation = SimPEGGroup.create(workspace, name="plate simulation") + fwr_file = BaseUIJson.read(options.default_ui_json) + + fwr_file.inversion_type = "gravity" + fwr_file.forward_only = True + fwr_file.geoh5 = str(workspace.h5file) + fwr_file.topography_object.value = str(topo.uid) + fwr_file.data_object.value = str(data.uid) + + gravity = fwr_file.to_ui_json_group(workspace=workspace, name="gravity fwd") + options = PlateSimulationOptions.model_construct() - plate_ifile = InputFile.read_ui_json(options.default_ui_json) - options_dict = plate_ifile.ui_json - options_dict["simulation"]["value"] = str(gravity.uid) - options_dict["overburden_property"]["value"] = 100.0 - options_dict["thickness"]["value"] = 20.0 - options_dict["u_cell_size"]["value"] = 10.0 - options_dict["v_cell_size"]["value"] = 10.0 - options_dict["w_cell_size"]["value"] = 10.0 - options_dict["depth_core"]["value"] = 400.0 - options_dict["minimum_level"]["value"] = 8 - options_dict["max_distance"]["value"] = 200.0 - options_dict["diagonal_balance"]["value"] = False - options_dict["padding_distance"]["value"] = 1500.0 - options_dict["dip_direction"]["value"] = 0.0 - options_dict["number"]["value"] = 1 - options_dict["elevation"]["value"] = 100.0 - options_dict["out_group"]["value"] = str(simulation.uid) - simulation.options = options_dict + plate_ifile = BaseUIJson.read(options.default_ui_json) + + plate_ifile.simulation.value = str(gravity.uid) + plate_ifile.overburden_property.value = 100.0 + plate_ifile.thickness.value = 20.0 + plate_ifile.u_cell_size.value = 10.0 + plate_ifile.v_cell_size.value = 10.0 + plate_ifile.w_cell_size.value = 10.0 + plate_ifile.depth_core.value = 400.0 + plate_ifile.minimum_level.value = 8 + plate_ifile.max_distance.value = 200.0 + plate_ifile.diagonal_balance.value = False + plate_ifile.padding_distance.value = 1500.0 + plate_ifile.dip_direction.value = 0.0 + plate_ifile.number.value = 1 + plate_ifile.elevation.value = 100.0 + + simulation = plate_ifile.to_ui_json_group( + workspace=workspace, name="plate simulation" + ) return simulation @@ -69,30 +69,30 @@ def test_sweep(tmp_path): with Workspace.create(tmp_path / "test.geoh5") as ws: plate_simulation = setup_plate_sweep(ws) - ifile = InputFile.read_ui_json( - assets_path() / "uijson" / "plate_sweep.ui.json", validate=False - ) - ifile.data["name"] = "test_gravity_plate_simulation" - ifile.data["geoh5"] = ws - ifile.data["template"] = str(plate_simulation.uid) - ifile.data["workdir"] = str(workdir) - ifile.data["background_start"] = 0.0 - ifile.data["background_stop"] = 100.0 - ifile.data["background_count"] = 2 - ifile.data["plate_start"] = 500.0 - ifile.data["plate_stop"] = 1000.0 - ifile.data["plate_count"] = 2 - ifile.data["out_group"] = None - - ifile.write_ui_json(name="plate_sweep.ui.json", path=tmp_path) + ifile = BaseUIJson.read(assets_path() / "uijson" / "plate_sweep.ui.json") + data = { + "name": "test_gravity_plate_simulation", + "geoh5": ws, + "template": str(plate_simulation.uid), + "workdir": str(workdir), + "background_start": 0.0, + "background_stop": 100.0, + "background_count": 2, + "plate_start": 500.0, + "plate_stop": 1000.0, + "plate_count": 2, + "out_group": None, + } + ifile.set_values(**data) + ifile.write(tmp_path / "plate_sweep.ui.json") PlateSweepDriver.start(tmp_path / "plate_sweep.ui.json") assert workdir.exists() with Workspace(tmp_path / "test.geoh5"): - ifile = InputFile.read_ui_json(tmp_path / "plate_sweep.ui.json") - ifile.set_data_value("background_count", 3) - ifile.write_ui_json(path=tmp_path, name="plate_sweep_modified.ui.json") + ifile = BaseUIJson.read(tmp_path / "plate_sweep.ui.json") + ifile.set_values(background_count=3) + ifile.write(tmp_path / "plate_sweep_modified.ui.json") PlateSweepDriver.start(tmp_path / "plate_sweep_modified.ui.json") From 084059f8c18e78523feee1bbeec40eb442f4a0a3 Mon Sep 17 00:00:00 2001 From: domfournier Date: Mon, 27 Apr 2026 13:53:05 -0700 Subject: [PATCH 16/24] Finish removing InputFile from tests --- tests/plate_simulation/runtest/driver_test.py | 87 ++++++++++--------- tests/plate_simulation/runtest/match_test.py | 11 +-- 2 files changed, 48 insertions(+), 50 deletions(-) diff --git a/tests/plate_simulation/runtest/driver_test.py b/tests/plate_simulation/runtest/driver_test.py index 297237c4..6b2f9432 100644 --- a/tests/plate_simulation/runtest/driver_test.py +++ b/tests/plate_simulation/runtest/driver_test.py @@ -11,7 +11,7 @@ import logging from geoh5py.groups import SimPEGGroup -from geoh5py.ui_json import InputFile +from geoh5py.ui_json import BaseUIJson from simpeg_drivers import assets_path from simpeg_drivers.plate_simulation.driver import ( @@ -31,6 +31,7 @@ SurveyOptions, SyntheticsComponentsOptions, ) +from simpeg_drivers.utils.utils import validate_out_group from tests.utils.targets import get_workspace @@ -45,52 +46,52 @@ def test_plate_simulation_params_from_input_file(tmp_path, caplog): with get_workspace(tmp_path / "inversion_test.ui.geoh5") as geoh5: components = SyntheticsComponents(geoh5, options=opts) - ifile = InputFile.read_ui_json( - assets_path() / "uijson" / "plate_simulation.ui.json", validate=False - ) - ifile.data["name"] = "test_gravity_plate_simulation" - ifile.data["geoh5"] = geoh5 - # Add simulation parameter - gravity_inversion = SimPEGGroup.create(geoh5) - options = GravityForwardOptions.model_construct() - fwr_ifile = InputFile.read_ui_json(options.default_ui_json) - options_dict = fwr_ifile.ui_json - options_dict["inversion_type"] = "gravity" - options_dict["forward_only"] = True - options_dict["geoh5"] = str(geoh5.h5file) - options_dict["topography_object"]["value"] = str(components.topography.uid) - options_dict["data_object"]["value"] = str(components.survey.uid) - gravity_inversion.options = options_dict - ifile.data["simulation"] = gravity_inversion - - # Add mesh parameters - ifile.data["u_cell_size"] = 10.0 - ifile.data["v_cell_size"] = 10.0 - ifile.data["w_cell_size"] = 10.0 - ifile.data["depth_core"] = 400.0 - ifile.data["minimum_level"] = 8 - ifile.data["max_distance"] = 200.0 - ifile.data["diagonal_balance"] = False - ifile.data["padding_distance"] = 1500.0 - - # Add model parameters - ifile.data["background"] = 1000.0 - ifile.data["overburden_property"] = 5.0 - ifile.data["thickness"] = 50.0 - ifile.data["plate_property"] = 2.0 - ifile.data["width"] = 100.0 - ifile.data["strike_length"] = 100.0 - ifile.data["dip_length"] = 100.0 - ifile.data["dip"] = 0.0 - ifile.data["dip_direction"] = 0.0 - ifile.data["number"] = 9 - ifile.data["spacing"] = 10.0 - ifile.data["elevation"] = 20 + fwr_ifile = BaseUIJson.read(options.default_ui_json) + options_dict = { + "inversion_type": "gravity", + "forward_only": True, + "topography_object": str(components.topography.uid), + "data_object": str(components.survey.uid), + "title": "gravity fwd", + } + fwr_ifile.set_values(**options_dict) + options_dict = fwr_ifile.to_params(workspace=geoh5) + options = GravityForwardOptions.build(options_dict) + gravity_inversion = validate_out_group(options) + + ifile = BaseUIJson.read(assets_path() / "uijson" / "plate_simulation.ui.json") + options_dict = { + "simulation": gravity_inversion, + # Add mesh parameters + "u_cell_size": 10.0, + "v_cell_size": 10.0, + "w_cell_size": 10.0, + "depth_core": 400.0, + "minimum_level": 8, + "max_distance": 200.0, + "diagonal_balance": False, + "padding_distance": 1500.0, + "name": "test_gravity_plate_simulation", + # Add model parameters + "background": 1000.0, + "overburden_property": 5.0, + "thickness": 50.0, + "plate_property": 2.0, + "width": 100.0, + "strike_length": 100.0, + "dip_length": 100.0, + "dip": 0.0, + "dip_direction": 0.0, + "number": 9, + "spacing": 10.0, + "elevation": 20, + } + ifile.set_values(**options_dict) with caplog.at_level(logging.WARNING): - params = PlateSimulationOptions.build(ifile) + params = PlateSimulationOptions.build(ifile.to_params(workspace=geoh5)) assert "Overburden thickness exceeds the plate depth" in caplog.text assert isinstance(params.simulation, SimPEGGroup) diff --git a/tests/plate_simulation/runtest/match_test.py b/tests/plate_simulation/runtest/match_test.py index 3841b267..e9619bcd 100644 --- a/tests/plate_simulation/runtest/match_test.py +++ b/tests/plate_simulation/runtest/match_test.py @@ -19,7 +19,7 @@ from geoh5py.data import FilenameData from geoh5py.groups import PropertyGroup, SimPEGGroup from geoh5py.objects import Points -from geoh5py.ui_json import InputFile +from geoh5py.ui_json import BaseUIJson from scipy import signal from simpeg_drivers import assets_path @@ -146,13 +146,10 @@ def test_matching_driver(tmp_path: Path): fwr_driver = TDEMForwardDriver(params) - ifile = InputFile.read_ui_json( - assets_path() / "uijson" / "plate_simulation.ui.json", validate=False - ) - ifile.data["geoh5"] = geoh5 - ifile.data["simulation"] = fwr_driver.out_group + ifile = BaseUIJson.read(assets_path() / "uijson" / "plate_simulation.ui.json") + ifile.set_values(simulation=fwr_driver.out_group) - plate_options = PlateSimulationOptions.build(ifile.data) + plate_options = PlateSimulationOptions.build(ifile.to_params(workspace=geoh5)) plate_options.model.overburden_options.thickness = 25.0 plate_options.model.overburden_options.overburden_property = 10000 plate_options.model.plate_options.geometry.dip_length = 300.0 From 21741bf584ea338381c620db2af6d5855f21af85 Mon Sep 17 00:00:00 2001 From: domfournier Date: Mon, 27 Apr 2026 15:34:45 -0700 Subject: [PATCH 17/24] Re-implement BaseUIJson.read to deal with bad legacy files. --- simpeg_drivers/driver.py | 5 +++- simpeg_drivers/options.py | 6 +++-- .../plate_simulation/match/driver.py | 6 ++--- simpeg_drivers/plate_simulation/options.py | 4 +-- .../plate_simulation/sweep/options.py | 4 +-- simpeg_drivers/uijson.py | 23 ++++++++++++++++ simpeg_drivers/utils/utils.py | 4 +-- tests/uijson_test.py | 27 +++++++++---------- 8 files changed, 53 insertions(+), 26 deletions(-) diff --git a/simpeg_drivers/driver.py b/simpeg_drivers/driver.py index 120625a3..c31e79ab 100644 --- a/simpeg_drivers/driver.py +++ b/simpeg_drivers/driver.py @@ -70,6 +70,7 @@ BaseInversionOptions, ) from simpeg_drivers.joint.options import BaseJointOptions +from simpeg_drivers.uijson import SimPEGDriversUIJson 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 ( @@ -505,7 +506,9 @@ def start(cls, filepath: str | Path | BaseUIJson, mode="r+", **kwargs) -> Self: """ uijson = ( - BaseUIJson.read(filepath) if isinstance(filepath, str | Path) else filepath + SimPEGDriversUIJson.read(filepath) + if isinstance(filepath, str | Path) + else filepath ) if not isinstance(uijson, BaseUIJson): diff --git a/simpeg_drivers/options.py b/simpeg_drivers/options.py index f29960f5..446cf5e4 100644 --- a/simpeg_drivers/options.py +++ b/simpeg_drivers/options.py @@ -42,8 +42,10 @@ model_validator, ) +from simpeg_drivers.uijson import SimPEGDriversUIJson +from simpeg_drivers.utils.regularization import direction_and_dip + from . import public_version -from .utils.regularization import direction_and_dip logger = getLogger(__name__) @@ -243,7 +245,7 @@ def write_ui_json(self, path: Path) -> Path: TODO: Replace in favor of base Options implementation after geoapps_utils@feature/uijson is merged """ - ui_json = BaseUIJson.read(self.default_ui_json) + ui_json = SimPEGDriversUIJson.read(self.default_ui_json) flatten = recursive_flatten(self.model_dump(exclude_unset=True)) ui_json.set_values(**flatten) diff --git a/simpeg_drivers/plate_simulation/match/driver.py b/simpeg_drivers/plate_simulation/match/driver.py index 3e61daa4..d7e72650 100644 --- a/simpeg_drivers/plate_simulation/match/driver.py +++ b/simpeg_drivers/plate_simulation/match/driver.py @@ -33,7 +33,6 @@ from geoh5py.groups import PropertyGroup, SimPEGGroup from geoh5py.objects import AirborneTEMReceivers, MaxwellPlate, Surface from geoh5py.objects.maxwell_plate import PlateGeometry -from geoh5py.ui_json import BaseUIJson from scipy import ndimage, signal from scipy.sparse import csr_matrix from scipy.spatial import cKDTree @@ -42,6 +41,7 @@ from simpeg_drivers.electromagnetics.time_domain.options import CONVERSION from simpeg_drivers.plate_simulation.match.options import PlateMatchOptions from simpeg_drivers.plate_simulation.options import ModelOptions, PlateSimulationOptions +from simpeg_drivers.uijson import SimPEGDriversUIJson from simpeg_drivers.utils.utils import ( get_default_parallelization_params, start_dask_run, @@ -166,7 +166,7 @@ def start(cls, filepath: str | Path, mode="r+", **_) -> Self: filepath = Path(filepath).resolve() # TODO: Replace with UIJson when fully implemented - uijson = BaseUIJson.read(filepath) + uijson = SimPEGDriversUIJson.read(filepath) with uijson.geoh5.open(mode=mode): try: @@ -386,7 +386,7 @@ def run(self): ui_json_dict = survey.parent.parent.options ui_json_dict["geoh5"] = ws - uijson = BaseUIJson.from_dict(ui_json_dict) + uijson = SimPEGDriversUIJson.from_dict(ui_json_dict) # Avoid getting pydantic deprecation warnings from old PlateSimulations stored with suppress_logging(): diff --git a/simpeg_drivers/plate_simulation/options.py b/simpeg_drivers/plate_simulation/options.py index 3148ceb5..28d47ec0 100644 --- a/simpeg_drivers/plate_simulation/options.py +++ b/simpeg_drivers/plate_simulation/options.py @@ -15,10 +15,10 @@ from geoapps_utils.base import Options from geoh5py.groups import SimPEGGroup, UIJsonGroup from geoh5py.shared.utils import fetch_active_workspace -from geoh5py.ui_json import BaseUIJson from simpeg_drivers import assets_path from simpeg_drivers.options import BaseForwardOptions +from simpeg_drivers.uijson import SimPEGDriversUIJson from simpeg_drivers.utils.synthetics.meshes import MeshOptions from simpeg_drivers.utils.utils import driver_class_from_dict @@ -60,7 +60,7 @@ def simulation_parameters(self) -> BaseForwardOptions: simulation_options = deepcopy(self.simulation.options) simulation_options["geoh5"] = self.geoh5 - ui_json = BaseUIJson.from_dict(simulation_options) + ui_json = SimPEGDriversUIJson.from_dict(simulation_options) with fetch_active_workspace(self.geoh5) as workspace: data = ui_json.to_params(workspace=workspace, validate=False) diff --git a/simpeg_drivers/plate_simulation/sweep/options.py b/simpeg_drivers/plate_simulation/sweep/options.py index 017b93c5..2995cd68 100644 --- a/simpeg_drivers/plate_simulation/sweep/options.py +++ b/simpeg_drivers/plate_simulation/sweep/options.py @@ -17,10 +17,10 @@ from geoh5py import Workspace from geoh5py.groups import SimPEGGroup, UIJsonGroup from geoh5py.shared.utils import fetch_active_workspace, stringify -from geoh5py.ui_json import BaseUIJson from pydantic import BaseModel, ConfigDict, field_serializer, field_validator from simpeg_drivers import assets_path +from simpeg_drivers.uijson import SimPEGDriversUIJson class ParamSweep(BaseModel): @@ -151,7 +151,7 @@ def all_hashable_options(options: dict, workspace: Workspace) -> dict: :param options: Options dictionary :param workspace: Workspace to fetch objects from. """ - ifile = BaseUIJson.from_dict(options) + ifile = SimPEGDriversUIJson.from_dict(options) with fetch_active_workspace(workspace, mode="r") as ws: data = ifile.to_params(workspace=ws, validate=False) diff --git a/simpeg_drivers/uijson.py b/simpeg_drivers/uijson.py index c9495195..65a91a30 100644 --- a/simpeg_drivers/uijson.py +++ b/simpeg_drivers/uijson.py @@ -84,3 +84,26 @@ def write_default(cls): data = uijson.model_dump_json(indent=4, exclude_unset=False) with open(cls.default_ui_json, "w", encoding="utf-8") as file: file.write(data) + + @classmethod + def from_dict(cls, data: dict) -> BaseUIJson: + """ + Create a UIJson instance from a dictionary. + + Deal with known issues in legacy files + + :param data: Dictionary representing the ui json object. + + :returns: UIJson object. + """ + kwargs = {} + for key, item in data.items(): + if isinstance(item, dict) and key == "tile_spatial": + item.pop("isValue", None) + item.pop("property", None) + + kwargs[key] = item if item != "" else None + + ui_json_class = cls.infer(**kwargs) + + return ui_json_class(**kwargs) diff --git a/simpeg_drivers/utils/utils.py b/simpeg_drivers/utils/utils.py index 0566dfa6..bb488316 100644 --- a/simpeg_drivers/utils/utils.py +++ b/simpeg_drivers/utils/utils.py @@ -40,12 +40,12 @@ from geoh5py.objects.surveys.electromagnetics.base import LargeLoopGroundEMSurvey from geoh5py.shared import INTEGER_NDV from geoh5py.shared.utils import fetch_active_workspace, mask_by_extent, stringify -from geoh5py.ui_json import BaseUIJson from grid_apps.utils import octree_2_treemesh from scipy.interpolate import interp1d from scipy.spatial import ConvexHull, cKDTree from simpeg_drivers import DRIVER_MAP +from simpeg_drivers.uijson import SimPEGDriversUIJson if TYPE_CHECKING: @@ -610,7 +610,7 @@ def simpeg_group_to_driver(group: SimPEGGroup, workspace: Workspace) -> Driver: """ ui_json_dict = deepcopy(group.options) ui_json_dict["geoh5"] = workspace - uijson = BaseUIJson.from_dict(ui_json_dict) + uijson = SimPEGDriversUIJson.from_dict(ui_json_dict) data = uijson.to_params(workspace) data["out_group"] = group diff --git a/tests/uijson_test.py b/tests/uijson_test.py index 6776d47d..f1eb88b9 100644 --- a/tests/uijson_test.py +++ b/tests/uijson_test.py @@ -18,7 +18,6 @@ from geoapps_utils.driver.data import BaseData from geoapps_utils.run import load_ui_json_as_dict from geoh5py import Workspace -from geoh5py.ui_json import BaseUIJson from geoh5py.ui_json.annotations import Deprecated from packaging.version import Version from pydantic import AliasChoices, Field @@ -277,7 +276,7 @@ def test_legacy_uijson(tmp_path: Path, caplog): version_path = tmp_path / directory.name for file in directory.glob("*.ui.json"): - ifile = BaseUIJson.read(file) + ifile = SimPEGDriversUIJson.read(file) inversion_type = ifile.inversion_type if inversion_type not in CHANNEL_NAME: @@ -304,19 +303,19 @@ def test_legacy_uijson(tmp_path: Path, caplog): ) with Workspace.create(work_path / "inversion_test.ui.geoh5") as geoh5: components = SyntheticsComponents(geoh5, options=opts) - data = ifile.to_params(workspace=geoh5, validate=False) - data["geoh5"] = geoh5 - data["mesh"] = components.mesh - data["starting_model"] = components.model - data["data_object"] = components.survey - data["topography_object"] = components.topography + options = ifile.to_params(workspace=geoh5, validate=False) + options["geoh5"] = geoh5 + options["mesh"] = components.mesh + options["starting_model"] = components.model + options["data_object"] = components.survey + options["topography_object"] = components.topography # Test deprecated name - data["coolingFactor"] = 4.0 + options["coolingFactor"] = 4.0 if "2d" in inversion_type or "pseudo 3d" in inversion_type: line_id = geoh5.get_entity("line_ids")[0] - data["line_object"] = line_id + options["line_object"] = line_id if not forward: n_vals = components.survey.n_vertices @@ -345,13 +344,13 @@ def test_legacy_uijson(tmp_path: Path, caplog): else: channel = data[0] - data[CHANNEL_NAME[inversion_type] + "_channel"] = channel - data[CHANNEL_NAME[inversion_type] + "_uncertainty"] = channel + options[CHANNEL_NAME[inversion_type] + "_channel"] = channel + options[CHANNEL_NAME[inversion_type] + "_uncertainty"] = channel - driver = driver_class_from_dict(data) + driver = driver_class_from_dict(options) with caplog.at_level(logging.WARNING): - params = driver._params_class.build(data) # pylint: disable=protected-access + params = driver._params_class.build(options) # pylint: disable=protected-access driver = driver(params) if "pseudo" in inversion_type: From 9b37b28665fc52120a69ae7bd5072443a8066514 Mon Sep 17 00:00:00 2001 From: domfournier Date: Mon, 27 Apr 2026 15:45:10 -0700 Subject: [PATCH 18/24] Bring back Ground mag option --- simpeg_drivers-assets/uijson/magnetic_scalar_forward.ui.json | 3 ++- simpeg_drivers-assets/uijson/magnetic_scalar_inversion.ui.json | 3 ++- simpeg_drivers-assets/uijson/magnetic_vector_forward.ui.json | 3 ++- simpeg_drivers-assets/uijson/magnetic_vector_inversion.ui.json | 3 ++- .../uijson/magnetic_vector_pde_forward.ui.json | 3 ++- .../uijson/magnetic_vector_pde_inversion.ui.json | 3 ++- 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/simpeg_drivers-assets/uijson/magnetic_scalar_forward.ui.json b/simpeg_drivers-assets/uijson/magnetic_scalar_forward.ui.json index 723cba86..f480bae4 100644 --- a/simpeg_drivers-assets/uijson/magnetic_scalar_forward.ui.json +++ b/simpeg_drivers-assets/uijson/magnetic_scalar_forward.ui.json @@ -98,7 +98,8 @@ "{f26feba3-aded-494b-b9e9-b2bbcbe298e1}", "{48f5054a-1c5c-4ca4-9048-80f36dc60a06}", "{b020a277-90e2-4cd7-84d6-612ee3f25051}", - "{4b99204c-d133-4579-a916-a9c8b98cfccb}" + "{4b99204c-d133-4579-a916-a9c8b98cfccb}", + "{028e4905-cc97-4dab-b1bf-d76f58b501b5}" ], "value": "" }, diff --git a/simpeg_drivers-assets/uijson/magnetic_scalar_inversion.ui.json b/simpeg_drivers-assets/uijson/magnetic_scalar_inversion.ui.json index f5d9f81d..2bd033de 100644 --- a/simpeg_drivers-assets/uijson/magnetic_scalar_inversion.ui.json +++ b/simpeg_drivers-assets/uijson/magnetic_scalar_inversion.ui.json @@ -47,7 +47,8 @@ "{f26feba3-aded-494b-b9e9-b2bbcbe298e1}", "{48f5054a-1c5c-4ca4-9048-80f36dc60a06}", "{b020a277-90e2-4cd7-84d6-612ee3f25051}", - "{4b99204c-d133-4579-a916-a9c8b98cfccb}" + "{4b99204c-d133-4579-a916-a9c8b98cfccb}", + "{028e4905-cc97-4dab-b1bf-d76f58b501b5}" ], "value": "" }, diff --git a/simpeg_drivers-assets/uijson/magnetic_vector_forward.ui.json b/simpeg_drivers-assets/uijson/magnetic_vector_forward.ui.json index 2ca14bad..f5950069 100644 --- a/simpeg_drivers-assets/uijson/magnetic_vector_forward.ui.json +++ b/simpeg_drivers-assets/uijson/magnetic_vector_forward.ui.json @@ -47,7 +47,8 @@ "{f26feba3-aded-494b-b9e9-b2bbcbe298e1}", "{48f5054a-1c5c-4ca4-9048-80f36dc60a06}", "{b020a277-90e2-4cd7-84d6-612ee3f25051}", - "{4b99204c-d133-4579-a916-a9c8b98cfccb}" + "{4b99204c-d133-4579-a916-a9c8b98cfccb}", + "{028e4905-cc97-4dab-b1bf-d76f58b501b5}" ], "value": "", "optional": true, diff --git a/simpeg_drivers-assets/uijson/magnetic_vector_inversion.ui.json b/simpeg_drivers-assets/uijson/magnetic_vector_inversion.ui.json index ec21e127..ea9cad09 100644 --- a/simpeg_drivers-assets/uijson/magnetic_vector_inversion.ui.json +++ b/simpeg_drivers-assets/uijson/magnetic_vector_inversion.ui.json @@ -47,7 +47,8 @@ "{f26feba3-aded-494b-b9e9-b2bbcbe298e1}", "{48f5054a-1c5c-4ca4-9048-80f36dc60a06}", "{b020a277-90e2-4cd7-84d6-612ee3f25051}", - "{4b99204c-d133-4579-a916-a9c8b98cfccb}" + "{4b99204c-d133-4579-a916-a9c8b98cfccb}", + "{028e4905-cc97-4dab-b1bf-d76f58b501b5}" ], "value": "" }, diff --git a/simpeg_drivers-assets/uijson/magnetic_vector_pde_forward.ui.json b/simpeg_drivers-assets/uijson/magnetic_vector_pde_forward.ui.json index 866970fe..d7d10023 100644 --- a/simpeg_drivers-assets/uijson/magnetic_vector_pde_forward.ui.json +++ b/simpeg_drivers-assets/uijson/magnetic_vector_pde_forward.ui.json @@ -46,7 +46,8 @@ "{6a057fdc-b355-11e3-95be-fd84a7ffcb88}", "{f26feba3-aded-494b-b9e9-b2bbcbe298e1}", "{48f5054a-1c5c-4ca4-9048-80f36dc60a06}", - "{b020a277-90e2-4cd7-84d6-612ee3f25051}" + "{b020a277-90e2-4cd7-84d6-612ee3f25051}", + "{028e4905-cc97-4dab-b1bf-d76f58b501b5}" ], "value": "", "optional": true, diff --git a/simpeg_drivers-assets/uijson/magnetic_vector_pde_inversion.ui.json b/simpeg_drivers-assets/uijson/magnetic_vector_pde_inversion.ui.json index ea39ca71..a8cabc48 100644 --- a/simpeg_drivers-assets/uijson/magnetic_vector_pde_inversion.ui.json +++ b/simpeg_drivers-assets/uijson/magnetic_vector_pde_inversion.ui.json @@ -47,7 +47,8 @@ "{f26feba3-aded-494b-b9e9-b2bbcbe298e1}", "{48f5054a-1c5c-4ca4-9048-80f36dc60a06}", "{b020a277-90e2-4cd7-84d6-612ee3f25051}", - "{4b99204c-d133-4579-a916-a9c8b98cfccb}" + "{4b99204c-d133-4579-a916-a9c8b98cfccb}", + "{028e4905-cc97-4dab-b1bf-d76f58b501b5}" ], "value": "" }, From a429e3f93b36d056c22f82a37194a3b6a5a3742f Mon Sep 17 00:00:00 2001 From: domfournier Date: Mon, 27 Apr 2026 15:46:29 -0700 Subject: [PATCH 19/24] Add airborne and ground gravity survey in meshTypes --- simpeg_drivers-assets/uijson/gravity_inversion.ui.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/simpeg_drivers-assets/uijson/gravity_inversion.ui.json b/simpeg_drivers-assets/uijson/gravity_inversion.ui.json index 60f6d5fb..a5b1c119 100644 --- a/simpeg_drivers-assets/uijson/gravity_inversion.ui.json +++ b/simpeg_drivers-assets/uijson/gravity_inversion.ui.json @@ -16,7 +16,9 @@ "{6a057fdc-b355-11e3-95be-fd84a7ffcb88}", "{f26feba3-aded-494b-b9e9-b2bbcbe298e1}", "{48f5054a-1c5c-4ca4-9048-80f36dc60a06}", - "{b020a277-90e2-4cd7-84d6-612ee3f25051}" + "{b020a277-90e2-4cd7-84d6-612ee3f25051}", + "{b54f6be6-0eb5-4a4e-887a-ba9d276f9a83}", + "{5ffa3816-358d-4cdd-9b7d-e1f7f5543e05}" ], "value": "" }, From 08aea3d808e4e0ac975ddab4d134e76c4d6a3d6a Mon Sep 17 00:00:00 2001 From: domfournier Date: Tue, 28 Apr 2026 14:23:32 -0700 Subject: [PATCH 20/24] Docstrings --- simpeg_drivers/electricals/base_2d.py | 5 +++++ simpeg_drivers/plate_simulation/sweep/options.py | 2 ++ 2 files changed, 7 insertions(+) diff --git a/simpeg_drivers/electricals/base_2d.py b/simpeg_drivers/electricals/base_2d.py index 42242f90..ec06dcf7 100644 --- a/simpeg_drivers/electricals/base_2d.py +++ b/simpeg_drivers/electricals/base_2d.py @@ -40,6 +40,11 @@ class Conductivity2DModelOptions(ModelOptions): """ Options for the conductivity model used in all of EM methods. + + :param conductivity_model: Conductivity model or background conductivity value. + :param model_type: Either a 'conductivity' or 'resistivity' model. The default is 'conductivity'. + :param length_scale_y: Overloads length scales in y direction since not used in 2D inversions. + :param y_norm: Overloads norm in the y direction since not used in 2D inversions. """ model_type: ModelTypeEnum = ModelTypeEnum.conductivity diff --git a/simpeg_drivers/plate_simulation/sweep/options.py b/simpeg_drivers/plate_simulation/sweep/options.py index 2995cd68..8165051e 100644 --- a/simpeg_drivers/plate_simulation/sweep/options.py +++ b/simpeg_drivers/plate_simulation/sweep/options.py @@ -150,6 +150,8 @@ def all_hashable_options(options: dict, workspace: Workspace) -> dict: :param options: Options dictionary :param workspace: Workspace to fetch objects from. + + :return: Flat dictionary of all key/values. """ ifile = SimPEGDriversUIJson.from_dict(options) From 34f67df55549e8d6d4cb0220ac22c3f1bfa063e1 Mon Sep 17 00:00:00 2001 From: domfournier Date: Tue, 28 Apr 2026 14:59:21 -0700 Subject: [PATCH 21/24] Suppress parent from tile_spatial form --- simpeg_drivers/uijson.py | 1 + 1 file changed, 1 insertion(+) diff --git a/simpeg_drivers/uijson.py b/simpeg_drivers/uijson.py index 65a91a30..809c0e1a 100644 --- a/simpeg_drivers/uijson.py +++ b/simpeg_drivers/uijson.py @@ -101,6 +101,7 @@ def from_dict(cls, data: dict) -> BaseUIJson: if isinstance(item, dict) and key == "tile_spatial": item.pop("isValue", None) item.pop("property", None) + item.pop("parent", None) kwargs[key] = item if item != "" else None From 3b92bd862c2a7d39b3089af89aeae1c730202e21 Mon Sep 17 00:00:00 2001 From: domfournier Date: Wed, 29 Apr 2026 14:26:09 -0700 Subject: [PATCH 22/24] Fix start method of plate_match, run test the start --- simpeg_drivers/plate_simulation/match/driver.py | 9 +++++---- tests/plate_simulation/runtest/match_test.py | 10 +++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/simpeg_drivers/plate_simulation/match/driver.py b/simpeg_drivers/plate_simulation/match/driver.py index d7e72650..65f4cd3d 100644 --- a/simpeg_drivers/plate_simulation/match/driver.py +++ b/simpeg_drivers/plate_simulation/match/driver.py @@ -168,14 +168,15 @@ def start(cls, filepath: str | Path, mode="r+", **_) -> Self: # TODO: Replace with UIJson when fully implemented uijson = SimPEGDriversUIJson.read(filepath) - with uijson.geoh5.open(mode=mode): + with Workspace(uijson.geoh5, mode=mode) as workspace: try: - data = uijson.to_params(uijson.geoh5) + data = uijson.to_params(workspace) + options = PlateMatchOptions.build(**data) logger.info("Initializing application . . .") - driver = cls(**data) + driver = cls(options) logger.info("Running application . . .") driver.run() - logger.info("Results saved to %s", uijson.geoh5.h5file) + logger.info("Results saved to %s", uijson.geoh5) except GeoAppsError as error: logger.warning("\n\nApplicationError: %s\n\n", error) diff --git a/tests/plate_simulation/runtest/match_test.py b/tests/plate_simulation/runtest/match_test.py index e9619bcd..2a6770ae 100644 --- a/tests/plate_simulation/runtest/match_test.py +++ b/tests/plate_simulation/runtest/match_test.py @@ -219,13 +219,17 @@ def test_matching_driver(tmp_path: Path): topography_object=components.topography, simulations=new_dir, ) - match_driver = PlateMatchDriver(options) - results = match_driver.run() + json_file = options.write_ui_json(tmp_path / "match_options.ui.json") + PlateMatchDriver.start(json_file) + + with geoh5.open(): + out_group = geoh5.get_entity("Plate Match")[0] + results = out_group.get_entity("Points")[0] assert isinstance(results, Points) names = results.get_data("file")[0] - assert names.values[0] == file.stem + f"_[{1}].geoh5" + assert names.values == file.stem + f"_[{1}].geoh5" plate = geoh5.get_entity("Query [0]")[0] assert plate.geometry.dip_direction == 45.0 From 5fb7c6725f2d575c91c3bd1d6ee19d04895ede24 Mon Sep 17 00:00:00 2001 From: domfournier Date: Wed, 29 Apr 2026 14:45:16 -0700 Subject: [PATCH 23/24] Log the error from the start --- simpeg_drivers/driver.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/simpeg_drivers/driver.py b/simpeg_drivers/driver.py index c31e79ab..4fb1b058 100644 --- a/simpeg_drivers/driver.py +++ b/simpeg_drivers/driver.py @@ -512,7 +512,7 @@ def start(cls, filepath: str | Path | BaseUIJson, mode="r+", **kwargs) -> Self: ) if not isinstance(uijson, BaseUIJson): - raise TypeError("Input file must be a string path or an BaseUIJson object.") + raise TypeError("Input file must be a string path or a BaseUIJson object.") if uijson.geoh5 is None: raise GeoAppsError("The application needs a valid 'geoh5' file.") @@ -525,6 +525,9 @@ def start(cls, filepath: str | Path | BaseUIJson, mode="r+", **kwargs) -> Self: driver = cls(params) driver.run() except GeoAppsError as error: + logging.getLogger(__name__).warning( + "\n\nApplicationError: %s\n\n", error + ) sys.exit(1) return driver From 73e3ffbba9a0682ca33a965da3d876495b62ea08 Mon Sep 17 00:00:00 2001 From: domfournier Date: Wed, 29 Apr 2026 14:47:19 -0700 Subject: [PATCH 24/24] Fixes from copilot --- simpeg_drivers-assets/uijson/plate_sweep.ui.json | 4 ++-- simpeg_drivers/electricals/base_2d.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/simpeg_drivers-assets/uijson/plate_sweep.ui.json b/simpeg_drivers-assets/uijson/plate_sweep.ui.json index b85dffa0..6673d41e 100644 --- a/simpeg_drivers-assets/uijson/plate_sweep.ui.json +++ b/simpeg_drivers-assets/uijson/plate_sweep.ui.json @@ -25,7 +25,7 @@ "label": "Output directory", "enabled": false, "optional": true, - "tootip": "Directory to store simulation results, relative to the working geoh5.", + "tooltip": "Directory to store simulation results, relative to the working geoh5.", "value": "./simulations" }, "generate_summary": { @@ -33,7 +33,7 @@ "group": "Base", "label": "Generate summary file (.xlsx)", "enabled": true, - "tootip": "Create an xlsx file summarizing the parameters of all simulations found in the 'Output directory'.", + "tooltip": "Create an xlsx file summarizing the parameters of all simulations found in the 'Output directory'.", "value": true }, "background_start": { diff --git a/simpeg_drivers/electricals/base_2d.py b/simpeg_drivers/electricals/base_2d.py index ec06dcf7..b5a4553b 100644 --- a/simpeg_drivers/electricals/base_2d.py +++ b/simpeg_drivers/electricals/base_2d.py @@ -39,7 +39,7 @@ class Conductivity2DModelOptions(ModelOptions): """ - Options for the conductivity model used in all of EM methods. + Options for the conductivity model of 2D inverse problems. :param conductivity_model: Conductivity model or background conductivity value. :param model_type: Either a 'conductivity' or 'resistivity' model. The default is 'conductivity'.