From 2adc1d312260414c1421767152d01fa4aa75dc09 Mon Sep 17 00:00:00 2001 From: yumouwei <46117079+yumouwei@users.noreply.github.com> Date: Thu, 18 Dec 2025 11:53:39 -0500 Subject: [PATCH 01/17] Revamp d3d.get_n1_bradial_parameters method --- disruption_py/machine/d3d/physics.py | 66 ++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/disruption_py/machine/d3d/physics.py b/disruption_py/machine/d3d/physics.py index 2149ee69e..2e1f6540c 100644 --- a/disruption_py/machine/d3d/physics.py +++ b/disruption_py/machine/d3d/physics.py @@ -898,6 +898,72 @@ def get_z_parameters(params: PhysicsMethodParams): z_cur_norm = z_cur / nominal_flattop_radius return {"zcur": z_cur, "zcur_normalized": z_cur_norm} + @staticmethod + @physics_method( + columns=["n_equal_1_normalized", "n_equal_1_mode"], + tokamak=Tokamak.D3D, + ) + def get_n1_bradial_parameters(params: PhysicsMethodParams): + ''' + TODO: finish docstring + Docstring for get_n1_bradial_parameters + + :param params: Description + :type params: PhysicsMethodParams + + References + https://github.com/MIT-PSFC/disruption-py/issues/261 + https://github.com/MIT-PSFC/disruption-py/blob/matlab/DIII-D/get_n1_bradial_d3d.m + + This method was previously removed in v0.8 + ''' + # The following shots are missing bradial calculations in MDSplus and + # must be loaded from a separate datafile + # TODO: re-check these shot range + if 156199 <= params.shot_id <= 172430: + # TODO: load from tree files on disc + raise NotImplementedError + if 176030 <= params.shot_id <= 176912: + # TODO: load from netcdf file on disc + raise NotImplementedError + + try: + # Get data from the ONFR system + n_equal_1_mode, t_n1 = params.mds_conn.get_data_with_dims( + f"ptdata('onsbradial', {params.shot_id})", + ) # [G], [ms] # TODO: check unit + except mdsExceptions.MdsException: + try: + # Fallback: get data from the legacy DUD system + n_equal_1_mode, t_n1 = params.mds_conn.get_data_with_dims( + f"ptdata('dusbradial', {params.shot_id})", + ) # [G], [ms] # TODO: check unit + params.logger.verbose('n_equal_1_mode: Failed to get ONSBRADIAL signal. Use DUSBRADIAL instead.') + except mdsExceptions.MdsException: + params.logger.debug("n_equal_1_mode: Failed to get either ONSBRADIAL or DUSBRADIAL signal") + return {"n_equal_1_normalized": [np.nan], "n_equal_1_mode": [np.nan]} + t_n1 /= 1e3 # [ms] -> [s] + n_equal_1_mode = interp1(t_n1, n_equal_1_mode, params.times) + + # Calculate n_equal_1_normalized + # TODO: move b_tor calculation to an individual method? + # TODO: shouldn't we interpolate b_tor to n1_mode timebase, compute _norm, then interpolate to output timebase? + try: + b_tor, t_b_tor = params.mds_conn.get_data_with_dims( + f"ptdata('bt', {params.shot_id})" + ) # [ms], [T?] # TODO: check unit -- this should be identical to n1rms_norm computation + t_b_tor /= 1e3 # [ms] -> [s] + b_tor = interp1(t_b_tor, b_tor, params.times) # [T] + n_equal_1_normalized = n_equal_1_mode / np.abs(b_tor) + except mdsExceptions.MdsException as e: + params.logger.warning( + "Failed to get b_tor signal to compute n_equal_1_normalized" + ) + params.logger.opt(exception=True).debug(e) + n_equal_1_normalized = [np.nan] + # TODO: return btor? Yes in CMOD and EAST + return {"n_equal_1_mode": n_equal_1_mode, "n_equal_1_normalized": n_equal_1_normalized} + @staticmethod @physics_method(columns=["n1rms", "n1rms_normalized"], tokamak=Tokamak.D3D) def get_n1rms_parameters(params: PhysicsMethodParams): From c25e4fc87b13368a6235ed88dcbb4d8be71352bb Mon Sep 17 00:00:00 2001 From: yumouwei <46117079+yumouwei@users.noreply.github.com> Date: Thu, 18 Dec 2025 14:05:18 -0800 Subject: [PATCH 02/17] Test method on d3d server, then made improvements --- disruption_py/machine/d3d/physics.py | 39 ++++++++++++++++------------ 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/disruption_py/machine/d3d/physics.py b/disruption_py/machine/d3d/physics.py index 2e1f6540c..458645185 100644 --- a/disruption_py/machine/d3d/physics.py +++ b/disruption_py/machine/d3d/physics.py @@ -900,23 +900,23 @@ def get_z_parameters(params: PhysicsMethodParams): @staticmethod @physics_method( - columns=["n_equal_1_normalized", "n_equal_1_mode"], + columns=["n_equal_1_normalized", "n_equal_1_mode", "btor"], tokamak=Tokamak.D3D, ) def get_n1_bradial_parameters(params: PhysicsMethodParams): - ''' + """ TODO: finish docstring Docstring for get_n1_bradial_parameters - + :param params: Description :type params: PhysicsMethodParams - + References https://github.com/MIT-PSFC/disruption-py/issues/261 https://github.com/MIT-PSFC/disruption-py/blob/matlab/DIII-D/get_n1_bradial_d3d.m - + This method was previously removed in v0.8 - ''' + """ # The following shots are missing bradial calculations in MDSplus and # must be loaded from a separate datafile # TODO: re-check these shot range @@ -926,32 +926,36 @@ def get_n1_bradial_parameters(params: PhysicsMethodParams): if 176030 <= params.shot_id <= 176912: # TODO: load from netcdf file on disc raise NotImplementedError - + try: # Get data from the ONFR system n_equal_1_mode, t_n1 = params.mds_conn.get_data_with_dims( f"ptdata('onsbradial', {params.shot_id})", - ) # [G], [ms] # TODO: check unit + ) # [G], [ms] except mdsExceptions.MdsException: try: # Fallback: get data from the legacy DUD system n_equal_1_mode, t_n1 = params.mds_conn.get_data_with_dims( f"ptdata('dusbradial', {params.shot_id})", - ) # [G], [ms] # TODO: check unit - params.logger.verbose('n_equal_1_mode: Failed to get ONSBRADIAL signal. Use DUSBRADIAL instead.') + ) # [G], [ms] + params.logger.verbose( + "n_equal_1_mode: Failed to get ONSBRADIAL signal. Use DUSBRADIAL instead." + ) except mdsExceptions.MdsException: - params.logger.debug("n_equal_1_mode: Failed to get either ONSBRADIAL or DUSBRADIAL signal") + params.logger.warning( + "n_equal_1_mode: Failed to get either ONSBRADIAL or DUSBRADIAL signal" + ) return {"n_equal_1_normalized": [np.nan], "n_equal_1_mode": [np.nan]} - t_n1 /= 1e3 # [ms] -> [s] + t_n1 /= 1e3 # [ms] -> [s] n_equal_1_mode = interp1(t_n1, n_equal_1_mode, params.times) - + # Calculate n_equal_1_normalized # TODO: move b_tor calculation to an individual method? # TODO: shouldn't we interpolate b_tor to n1_mode timebase, compute _norm, then interpolate to output timebase? try: b_tor, t_b_tor = params.mds_conn.get_data_with_dims( f"ptdata('bt', {params.shot_id})" - ) # [ms], [T?] # TODO: check unit -- this should be identical to n1rms_norm computation + ) # [ms], [T] t_b_tor /= 1e3 # [ms] -> [s] b_tor = interp1(t_b_tor, b_tor, params.times) # [T] n_equal_1_normalized = n_equal_1_mode / np.abs(b_tor) @@ -961,8 +965,11 @@ def get_n1_bradial_parameters(params: PhysicsMethodParams): ) params.logger.opt(exception=True).debug(e) n_equal_1_normalized = [np.nan] - # TODO: return btor? Yes in CMOD and EAST - return {"n_equal_1_mode": n_equal_1_mode, "n_equal_1_normalized": n_equal_1_normalized} + return { + "n_equal_1_mode": n_equal_1_mode, + "n_equal_1_normalized": n_equal_1_normalized, + "btor": b_tor, + } @staticmethod @physics_method(columns=["n1rms", "n1rms_normalized"], tokamak=Tokamak.D3D) From ba7b1e797afceaffb3b2dab924a149740e9d3d81 Mon Sep 17 00:00:00 2001 From: yumouwei <46117079+yumouwei@users.noreply.github.com> Date: Thu, 18 Dec 2025 14:45:54 -0800 Subject: [PATCH 03/17] Move get_btor to its own physics method --- disruption_py/machine/d3d/physics.py | 66 ++++++++++++++++++---------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/disruption_py/machine/d3d/physics.py b/disruption_py/machine/d3d/physics.py index 458645185..9f8fb839a 100644 --- a/disruption_py/machine/d3d/physics.py +++ b/disruption_py/machine/d3d/physics.py @@ -898,9 +898,42 @@ def get_z_parameters(params: PhysicsMethodParams): z_cur_norm = z_cur / nominal_flattop_radius return {"zcur": z_cur, "zcur_normalized": z_cur_norm} + @staticmethod + @cache_method + @physics_method(columns=["btor"], tokamak=Tokamak.D3D) + def get_btor(params: PhysicsMethodParams): + """ + Calculate the toroidal magnetic field. + + Parameters + ---------- + params : PhysicsMethodParams + Parameters containing MDS connection and shot information + + Returns + ------- + dict + A dictionary containing the toroidal magnetic field (`btor`). + + References + ------- + # TODO: finish references + """ + try: + b_tor, t_b_tor = params.mds_conn.get_data_with_dims( + f"ptdata('bt', {params.shot_id})" + ) # [ms], [T] + except mdsExceptions.MdsException as e: + params.logger.warning("Failed to get b_tor signal") + params.logger.opt(exception=True).debug(e) + return {"btor": [np.nan]} + t_b_tor /= 1e3 # [ms] -> [s] + b_tor = interp1(t_b_tor, b_tor, params.times) + return {"btor": b_tor} + @staticmethod @physics_method( - columns=["n_equal_1_normalized", "n_equal_1_mode", "btor"], + columns=["n_equal_1_normalized", "n_equal_1_mode"], tokamak=Tokamak.D3D, ) def get_n1_bradial_parameters(params: PhysicsMethodParams): @@ -950,25 +983,16 @@ def get_n1_bradial_parameters(params: PhysicsMethodParams): n_equal_1_mode = interp1(t_n1, n_equal_1_mode, params.times) # Calculate n_equal_1_normalized - # TODO: move b_tor calculation to an individual method? - # TODO: shouldn't we interpolate b_tor to n1_mode timebase, compute _norm, then interpolate to output timebase? - try: - b_tor, t_b_tor = params.mds_conn.get_data_with_dims( - f"ptdata('bt', {params.shot_id})" - ) # [ms], [T] - t_b_tor /= 1e3 # [ms] -> [s] - b_tor = interp1(t_b_tor, b_tor, params.times) # [T] - n_equal_1_normalized = n_equal_1_mode / np.abs(b_tor) - except mdsExceptions.MdsException as e: + b_tor = D3DPhysicsMethods.get_btor(params)["btor"] + if np.isnan(b_tor).all(): params.logger.warning( "Failed to get b_tor signal to compute n_equal_1_normalized" ) - params.logger.opt(exception=True).debug(e) n_equal_1_normalized = [np.nan] + n_equal_1_normalized = n_equal_1_mode / np.abs(b_tor) return { "n_equal_1_mode": n_equal_1_mode, "n_equal_1_normalized": n_equal_1_normalized, - "btor": b_tor, } @staticmethod @@ -998,21 +1022,15 @@ def get_n1rms_parameters(params: PhysicsMethodParams): n1rms, t_n1rms = params.mds_conn.get_data_with_dims(r"\n1rms", tree_name="d3d") n1rms *= 1.0e-4 # Gauss -> Tesla t_n1rms /= 1e3 # [ms] -> [s] - n1rms = interp1(t_n1rms, n1rms, params.times) + n1rms = interp1(t_n1rms, n1rms, params.times) # Calculate n1rms_norm - try: - b_tor, t_b_tor = params.mds_conn.get_data_with_dims( - f"ptdata('bt', {params.shot_id})" - ) - t_b_tor /= 1e3 # [ms] -> [s] - b_tor = interp1(t_b_tor, b_tor, params.times) # [T] - n1rms_norm = n1rms / np.abs(b_tor) - except mdsExceptions.MdsException as e: + b_tor = D3DPhysicsMethods.get_btor(params)["btor"] + if np.isnan(b_tor).all(): params.logger.warning( - "Failed to get b_tor signal to compute n1rms_normalized" + "Failed to get b_tor signal to compute n_equal_1_normalized" ) - params.logger.opt(exception=True).debug(e) n1rms_norm = [np.nan] + n1rms_norm = n1rms / np.abs(b_tor) return {"n1rms": n1rms, "n1rms_normalized": n1rms_norm} # TODO: Need to test and unblock recalculating peaking factors From 562e27e76fba518f9a560dc11e1ae5d49805a678 Mon Sep 17 00:00:00 2001 From: yumouwei <46117079+yumouwei@users.noreply.github.com> Date: Thu, 18 Dec 2025 14:46:13 -0800 Subject: [PATCH 04/17] Fix n_equal_1_mode unit --- disruption_py/machine/d3d/physics.py | 1 + 1 file changed, 1 insertion(+) diff --git a/disruption_py/machine/d3d/physics.py b/disruption_py/machine/d3d/physics.py index 9f8fb839a..047def789 100644 --- a/disruption_py/machine/d3d/physics.py +++ b/disruption_py/machine/d3d/physics.py @@ -980,6 +980,7 @@ def get_n1_bradial_parameters(params: PhysicsMethodParams): ) return {"n_equal_1_normalized": [np.nan], "n_equal_1_mode": [np.nan]} t_n1 /= 1e3 # [ms] -> [s] + n_equal_1_mode *= 1.0e-4 # [G] -> [T] n_equal_1_mode = interp1(t_n1, n_equal_1_mode, params.times) # Calculate n_equal_1_normalized From fbd4b24866946f2b7282ca79b9e529742f6d1f0e Mon Sep 17 00:00:00 2001 From: yumouwei <46117079+yumouwei@users.noreply.github.com> Date: Thu, 18 Dec 2025 14:56:43 -0800 Subject: [PATCH 05/17] Add docstrings --- disruption_py/machine/d3d/physics.py | 36 ++++++++++++++++++---------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/disruption_py/machine/d3d/physics.py b/disruption_py/machine/d3d/physics.py index 047def789..dcc36b49f 100644 --- a/disruption_py/machine/d3d/physics.py +++ b/disruption_py/machine/d3d/physics.py @@ -917,7 +917,9 @@ def get_btor(params: PhysicsMethodParams): References ------- - # TODO: finish references + - original sources: [get_n1_bradial_d3d.m](https://github.com/MIT-PSFC/disruption + -py/blob/matlab/DIII-D/get_n1_bradial_d3d.m), [get_n1rms_d3d.m](https://github.com + /MIT-PSFC/disruption-py/blob/matlab/DIII-D/get_n1rms_d3d.m) """ try: b_tor, t_b_tor = params.mds_conn.get_data_with_dims( @@ -938,26 +940,34 @@ def get_btor(params: PhysicsMethodParams): ) def get_n1_bradial_parameters(params: PhysicsMethodParams): """ - TODO: finish docstring - Docstring for get_n1_bradial_parameters + This method obtaines the n=1 Bradial information (`n_equal_1_mode`) from + the ESLD coils (units of Tesla). It also calculates the normalized Bradial, + dividing by the toroidal B-field (`n_equal_1_normalized`). - :param params: Description - :type params: PhysicsMethodParams + Parameters + ---------- + params : PhysicsMethodParams + The parameters containing the MDSplus connection, shot id and more. - References - https://github.com/MIT-PSFC/disruption-py/issues/261 - https://github.com/MIT-PSFC/disruption-py/blob/matlab/DIII-D/get_n1_bradial_d3d.m + Returns + ------- + dict + A dictionary containing `n_equal_1_mode` and `n_equal_1_normalized`. - This method was previously removed in v0.8 + References + ------- + - original source: [get_n1_bradial_d3d.m](https://github.com/MIT-PSFC/disruption-py + /blob/matlab/DIII-D/get_n1_bradial_d3d.m) + - pull requests: + - issues: #[261](https://github.com/MIT-PSFC/disruption-py/issues/261) """ # The following shots are missing bradial calculations in MDSplus and # must be loaded from a separate datafile - # TODO: re-check these shot range if 156199 <= params.shot_id <= 172430: - # TODO: load from tree files on disc + # TODO: load data from custom tree structures raise NotImplementedError if 176030 <= params.shot_id <= 176912: - # TODO: load from netcdf file on disc + # TODO: load data from NetCDF file raise NotImplementedError try: @@ -1023,7 +1033,7 @@ def get_n1rms_parameters(params: PhysicsMethodParams): n1rms, t_n1rms = params.mds_conn.get_data_with_dims(r"\n1rms", tree_name="d3d") n1rms *= 1.0e-4 # Gauss -> Tesla t_n1rms /= 1e3 # [ms] -> [s] - n1rms = interp1(t_n1rms, n1rms, params.times) + n1rms = interp1(t_n1rms, n1rms, params.times) # Calculate n1rms_norm b_tor = D3DPhysicsMethods.get_btor(params)["btor"] if np.isnan(b_tor).all(): From 19f6fd6dd49b341ea1c3ab89d8916b99c38dc552 Mon Sep 17 00:00:00 2001 From: yumouwei <46117079+yumouwei@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:37:47 -0800 Subject: [PATCH 06/17] Add PR reference --- disruption_py/machine/d3d/physics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disruption_py/machine/d3d/physics.py b/disruption_py/machine/d3d/physics.py index dcc36b49f..1340ce766 100644 --- a/disruption_py/machine/d3d/physics.py +++ b/disruption_py/machine/d3d/physics.py @@ -958,7 +958,7 @@ def get_n1_bradial_parameters(params: PhysicsMethodParams): ------- - original source: [get_n1_bradial_d3d.m](https://github.com/MIT-PSFC/disruption-py /blob/matlab/DIII-D/get_n1_bradial_d3d.m) - - pull requests: + - pull requests: #[500](https://github.com/MIT-PSFC/disruption-py/pull/500) - issues: #[261](https://github.com/MIT-PSFC/disruption-py/issues/261) """ # The following shots are missing bradial calculations in MDSplus and From 4e35955d8eb2b0f70e31d0c51066e798dce32d24 Mon Sep 17 00:00:00 2001 From: yumouwei <46117079+yumouwei@users.noreply.github.com> Date: Tue, 21 Apr 2026 08:37:30 -0700 Subject: [PATCH 07/17] Change mds_conn to data_conn --- disruption_py/machine/d3d/physics.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/disruption_py/machine/d3d/physics.py b/disruption_py/machine/d3d/physics.py index 983471b9f..51947afcc 100644 --- a/disruption_py/machine/d3d/physics.py +++ b/disruption_py/machine/d3d/physics.py @@ -926,7 +926,7 @@ def get_btor(params: PhysicsMethodParams): /MIT-PSFC/disruption-py/blob/matlab/DIII-D/get_n1rms_d3d.m) """ try: - b_tor, t_b_tor = params.mds_conn.get_data_with_dims( + b_tor, t_b_tor = params.data_conn.get_data_with_dims( f"ptdata('bt', {params.shot_id})" ) # [ms], [T] except mdsExceptions.MdsException as e: @@ -976,13 +976,13 @@ def get_n1_bradial_parameters(params: PhysicsMethodParams): try: # Get data from the ONFR system - n_equal_1_mode, t_n1 = params.mds_conn.get_data_with_dims( + n_equal_1_mode, t_n1 = params.data_conn.get_data_with_dims( f"ptdata('onsbradial', {params.shot_id})", ) # [G], [ms] except mdsExceptions.MdsException: try: # Fallback: get data from the legacy DUD system - n_equal_1_mode, t_n1 = params.mds_conn.get_data_with_dims( + n_equal_1_mode, t_n1 = params.data_conn.get_data_with_dims( f"ptdata('dusbradial', {params.shot_id})", ) # [G], [ms] params.logger.verbose( From a325d255690e66dd9f389866104834a43b0efe88 Mon Sep 17 00:00:00 2001 From: yumouwei <46117079+yumouwei@users.noreply.github.com> Date: Wed, 22 Apr 2026 08:16:01 -0700 Subject: [PATCH 08/17] Minor changes suggestions by Copilot --- disruption_py/machine/d3d/physics.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/disruption_py/machine/d3d/physics.py b/disruption_py/machine/d3d/physics.py index 51947afcc..70248bd73 100644 --- a/disruption_py/machine/d3d/physics.py +++ b/disruption_py/machine/d3d/physics.py @@ -903,7 +903,6 @@ def get_z_parameters(params: PhysicsMethodParams): return {"zcur": z_cur, "zcur_normalized": z_cur_norm} @staticmethod - @cache_method @physics_method(columns=["btor"], tokamak=Tokamak.D3D) def get_btor(params: PhysicsMethodParams): """ @@ -928,7 +927,7 @@ def get_btor(params: PhysicsMethodParams): try: b_tor, t_b_tor = params.data_conn.get_data_with_dims( f"ptdata('bt', {params.shot_id})" - ) # [ms], [T] + ) # [T], [ms] except mdsExceptions.MdsException as e: params.logger.warning("Failed to get b_tor signal") params.logger.opt(exception=True).debug(e) @@ -944,9 +943,9 @@ def get_btor(params: PhysicsMethodParams): ) def get_n1_bradial_parameters(params: PhysicsMethodParams): """ - This method obtaines the n=1 Bradial information (`n_equal_1_mode`) from - the ESLD coils (units of Tesla). It also calculates the normalized Bradial, - dividing by the toroidal B-field (`n_equal_1_normalized`). + This method obtains the n=1 radial magnetic field information (`n_equal_1_mode`) from + the ESLD coils (units of Tesla). It also calculates the normalized radial magnetic field + by dividing by the toroidal B-field (`n_equal_1_normalized`). Parameters ---------- @@ -1004,7 +1003,8 @@ def get_n1_bradial_parameters(params: PhysicsMethodParams): "Failed to get b_tor signal to compute n_equal_1_normalized" ) n_equal_1_normalized = [np.nan] - n_equal_1_normalized = n_equal_1_mode / np.abs(b_tor) + else: + n_equal_1_normalized = n_equal_1_mode / np.abs(b_tor) return { "n_equal_1_mode": n_equal_1_mode, "n_equal_1_normalized": n_equal_1_normalized, @@ -1042,10 +1042,11 @@ def get_n1rms_parameters(params: PhysicsMethodParams): b_tor = D3DPhysicsMethods.get_btor(params)["btor"] if np.isnan(b_tor).all(): params.logger.warning( - "Failed to get b_tor signal to compute n_equal_1_normalized" + "Failed to get b_tor signal to compute n1rms_normalized" ) n1rms_norm = [np.nan] - n1rms_norm = n1rms / np.abs(b_tor) + else: + n1rms_norm = n1rms / np.abs(b_tor) return {"n1rms": n1rms, "n1rms_normalized": n1rms_norm} # TODO: Need to test and unblock recalculating peaking factors From 0dec33ef4069249d0e464b58ea7d1ffda3dcc440 Mon Sep 17 00:00:00 2001 From: yumouwei <46117079+yumouwei@users.noreply.github.com> Date: Tue, 28 Apr 2026 14:58:44 -0700 Subject: [PATCH 09/17] Simplify error catching and fallback logics --- disruption_py/machine/d3d/physics.py | 58 ++++++++++++---------------- 1 file changed, 25 insertions(+), 33 deletions(-) diff --git a/disruption_py/machine/d3d/physics.py b/disruption_py/machine/d3d/physics.py index 70248bd73..eef1717b1 100644 --- a/disruption_py/machine/d3d/physics.py +++ b/disruption_py/machine/d3d/physics.py @@ -924,14 +924,9 @@ def get_btor(params: PhysicsMethodParams): -py/blob/matlab/DIII-D/get_n1_bradial_d3d.m), [get_n1rms_d3d.m](https://github.com /MIT-PSFC/disruption-py/blob/matlab/DIII-D/get_n1rms_d3d.m) """ - try: - b_tor, t_b_tor = params.data_conn.get_data_with_dims( - f"ptdata('bt', {params.shot_id})" - ) # [T], [ms] - except mdsExceptions.MdsException as e: - params.logger.warning("Failed to get b_tor signal") - params.logger.opt(exception=True).debug(e) - return {"btor": [np.nan]} + b_tor, t_b_tor = params.data_conn.get_data_with_dims( + f"ptdata('bt', {params.shot_id})" + ) # [T], [ms] t_b_tor /= 1e3 # [ms] -> [s] b_tor = interp1(t_b_tor, b_tor, params.times) return {"btor": b_tor} @@ -979,32 +974,28 @@ def get_n1_bradial_parameters(params: PhysicsMethodParams): f"ptdata('onsbradial', {params.shot_id})", ) # [G], [ms] except mdsExceptions.MdsException: - try: - # Fallback: get data from the legacy DUD system - n_equal_1_mode, t_n1 = params.data_conn.get_data_with_dims( - f"ptdata('dusbradial', {params.shot_id})", - ) # [G], [ms] - params.logger.verbose( - "n_equal_1_mode: Failed to get ONSBRADIAL signal. Use DUSBRADIAL instead." - ) - except mdsExceptions.MdsException: - params.logger.warning( - "n_equal_1_mode: Failed to get either ONSBRADIAL or DUSBRADIAL signal" - ) - return {"n_equal_1_normalized": [np.nan], "n_equal_1_mode": [np.nan]} + # Fallback: get data from the legacy DUD system + params.logger.verbose( + "get_n1_bradial_parameters: Failed to get ONSBRADIAL signal. " + "Retrieving from DUSBRADIAL instead." + ) + n_equal_1_mode, t_n1 = params.data_conn.get_data_with_dims( + f"ptdata('dusbradial', {params.shot_id})", + ) # [G], [ms] t_n1 /= 1e3 # [ms] -> [s] n_equal_1_mode *= 1.0e-4 # [G] -> [T] n_equal_1_mode = interp1(t_n1, n_equal_1_mode, params.times) # Calculate n_equal_1_normalized - b_tor = D3DPhysicsMethods.get_btor(params)["btor"] - if np.isnan(b_tor).all(): + try: + b_tor = D3DPhysicsMethods.get_btor(params)["btor"] + n_equal_1_normalized = n_equal_1_mode / np.abs(b_tor) + except mdsExceptions.MdsException: params.logger.warning( - "Failed to get b_tor signal to compute n_equal_1_normalized" + "get_n1_bradial_parameters: Failed to get b_tor signal " + "to compute n_equal_1_normalized. Returning NaNs." ) - n_equal_1_normalized = [np.nan] - else: - n_equal_1_normalized = n_equal_1_mode / np.abs(b_tor) + n_equal_1_normalized = np.array([np.nan]) return { "n_equal_1_mode": n_equal_1_mode, "n_equal_1_normalized": n_equal_1_normalized, @@ -1039,14 +1030,15 @@ def get_n1rms_parameters(params: PhysicsMethodParams): t_n1rms /= 1e3 # [ms] -> [s] n1rms = interp1(t_n1rms, n1rms, params.times) # Calculate n1rms_norm - b_tor = D3DPhysicsMethods.get_btor(params)["btor"] - if np.isnan(b_tor).all(): + try: + b_tor = D3DPhysicsMethods.get_btor(params)["btor"] + n1rms_norm = n1rms / np.abs(b_tor) + except mdsExceptions.MdsException: params.logger.warning( - "Failed to get b_tor signal to compute n1rms_normalized" + "get_n1rms_parameters: Failed to get b_tor signal " + "to compute n1rms_normalized. Returning NaNs." ) - n1rms_norm = [np.nan] - else: - n1rms_norm = n1rms / np.abs(b_tor) + n1rms_norm = np.array([np.nan]) return {"n1rms": n1rms, "n1rms_normalized": n1rms_norm} # TODO: Need to test and unblock recalculating peaking factors From 3b0bec13ac7493154778b22fdc4612eec5857d7d Mon Sep 17 00:00:00 2001 From: yumouwei <46117079+yumouwei@users.noreply.github.com> Date: Mon, 4 May 2026 08:14:13 -0700 Subject: [PATCH 10/17] Allow loading data from NetCDF file on omega --- disruption_py/machine/d3d/physics.py | 41 ++++++++++++++++++---------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/disruption_py/machine/d3d/physics.py b/disruption_py/machine/d3d/physics.py index eef1717b1..fbcca566f 100644 --- a/disruption_py/machine/d3d/physics.py +++ b/disruption_py/machine/d3d/physics.py @@ -6,6 +6,7 @@ import numpy as np import scipy +import xarray as xr from disruption_py.core.physics_method.caching import cache_method from disruption_py.core.physics_method.decorator import physics_method @@ -965,23 +966,33 @@ def get_n1_bradial_parameters(params: PhysicsMethodParams): # TODO: load data from custom tree structures raise NotImplementedError if 176030 <= params.shot_id <= 176912: - # TODO: load data from NetCDF file - raise NotImplementedError - - try: - # Get data from the ONFR system - n_equal_1_mode, t_n1 = params.data_conn.get_data_with_dims( - f"ptdata('onsbradial', {params.shot_id})", - ) # [G], [ms] - except mdsExceptions.MdsException: - # Fallback: get data from the legacy DUD system + # Load data from a NetCDF file on Omega + # TODO: check if running on Omega, otherwise raise an error params.logger.verbose( - "get_n1_bradial_parameters: Failed to get ONSBRADIAL signal. " - "Retrieving from DUSBRADIAL instead." + "get_n1_bradial_parameters: Retrieving data from recalc.nc." + ) + filename = ( + "/fusion/projects/disruption_warning/software/recalc_bradial/recalc.nc" ) - n_equal_1_mode, t_n1 = params.data_conn.get_data_with_dims( - f"ptdata('dusbradial', {params.shot_id})", - ) # [G], [ms] + ds = xr.open_dataset(filename) + shot_data = ds.sel(shot=params.shot_id) + n_equal_1_mode = shot_data["dusbradial_calculated"].values.copy() # [G] + t_n1 = shot_data["times"].values.copy() # [ms] + else: + try: + # Get data from the ONFR system + n_equal_1_mode, t_n1 = params.data_conn.get_data_with_dims( + f"ptdata('onsbradial', {params.shot_id})", + ) # [G], [ms] + except mdsExceptions.MdsException: + # Fallback: get data from the legacy DUD system + params.logger.verbose( + "get_n1_bradial_parameters: Failed to get ONSBRADIAL signal. " + "Retrieving from DUSBRADIAL instead." + ) + n_equal_1_mode, t_n1 = params.data_conn.get_data_with_dims( + f"ptdata('dusbradial', {params.shot_id})", + ) # [G], [ms] t_n1 /= 1e3 # [ms] -> [s] n_equal_1_mode *= 1.0e-4 # [G] -> [T] n_equal_1_mode = interp1(t_n1, n_equal_1_mode, params.times) From 699f6cfa8970e18f90bd65b7f4ac2e9a572c094c Mon Sep 17 00:00:00 2001 From: yumouwei <46117079+yumouwei@users.noreply.github.com> Date: Mon, 4 May 2026 08:19:28 -0700 Subject: [PATCH 11/17] Move NetCDF file path to config --- disruption_py/machine/d3d/config.toml | 3 +++ disruption_py/machine/d3d/physics.py | 5 ++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/disruption_py/machine/d3d/config.toml b/disruption_py/machine/d3d/config.toml index 6221bc966..2ed1df1fa 100644 --- a/disruption_py/machine/d3d/config.toml +++ b/disruption_py/machine/d3d/config.toml @@ -10,6 +10,9 @@ driver = "FreeTDS" host = "d3drdb" port = 8001 +[d3d.physics.get_n1_bradial_parameters] +recalc_nc_path = "/fusion/projects/disruption_warning/software/recalc_bradial/recalc.nc" + [d3d.physics.time_domain_thresholds] dipprog_dt = 2e3 ip_prog = 100e3 diff --git a/disruption_py/machine/d3d/physics.py b/disruption_py/machine/d3d/physics.py index fbcca566f..0497798b9 100644 --- a/disruption_py/machine/d3d/physics.py +++ b/disruption_py/machine/d3d/physics.py @@ -8,6 +8,7 @@ import scipy import xarray as xr +from disruption_py.config import config from disruption_py.core.physics_method.caching import cache_method from disruption_py.core.physics_method.decorator import physics_method from disruption_py.core.physics_method.errors import CalculationError @@ -971,9 +972,7 @@ def get_n1_bradial_parameters(params: PhysicsMethodParams): params.logger.verbose( "get_n1_bradial_parameters: Retrieving data from recalc.nc." ) - filename = ( - "/fusion/projects/disruption_warning/software/recalc_bradial/recalc.nc" - ) + filename = config("d3d").physics.get_n1_bradial_parameters.recalc_nc_path ds = xr.open_dataset(filename) shot_data = ds.sel(shot=params.shot_id) n_equal_1_mode = shot_data["dusbradial_calculated"].values.copy() # [G] From 118742a058d95fb45d37e20731022a5f3c42f607 Mon Sep 17 00:00:00 2001 From: yumouwei <46117079+yumouwei@users.noreply.github.com> Date: Mon, 4 May 2026 09:07:43 -0700 Subject: [PATCH 12/17] Allow loading data from custom MDSplus trees --- disruption_py/machine/d3d/config.toml | 1 + disruption_py/machine/d3d/physics.py | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/disruption_py/machine/d3d/config.toml b/disruption_py/machine/d3d/config.toml index 2ed1df1fa..988ccb67b 100644 --- a/disruption_py/machine/d3d/config.toml +++ b/disruption_py/machine/d3d/config.toml @@ -12,6 +12,7 @@ port = 8001 [d3d.physics.get_n1_bradial_parameters] recalc_nc_path = "/fusion/projects/disruption_warning/software/recalc_bradial/recalc.nc" +recomputed_tree_path = "/fusion/projects/disruption_warning/software/recalc_bradial/bradial" [d3d.physics.time_domain_thresholds] dipprog_dt = 2e3 diff --git a/disruption_py/machine/d3d/physics.py b/disruption_py/machine/d3d/physics.py index 0497798b9..8316814a9 100644 --- a/disruption_py/machine/d3d/physics.py +++ b/disruption_py/machine/d3d/physics.py @@ -4,6 +4,9 @@ Module for retrieving and calculating data for DIII-D physics methods. """ +import os + +import MDSplus import numpy as np import scipy import xarray as xr @@ -964,9 +967,16 @@ def get_n1_bradial_parameters(params: PhysicsMethodParams): # The following shots are missing bradial calculations in MDSplus and # must be loaded from a separate datafile if 156199 <= params.shot_id <= 172430: - # TODO: load data from custom tree structures - raise NotImplementedError - if 176030 <= params.shot_id <= 176912: + # Load data from custom tree structures on Omega + # TODO: check if running on Omega, otherwise raise an error + os.environ["bradial_path"] = config( + "d3d" + ).physics.get_n1_bradial_parameters.recomputed_tree_path + tree = MDSplus.Tree("bradial", params.shot_id) + n_equal_1_mode = tree.getNode("dusbradial").data().squeeze() # [G] + t_n1 = tree.getNode("dusbradial").dim_of().data() # [ms] + # TODO: unset os.environ? + elif 176030 <= params.shot_id <= 176912: # Load data from a NetCDF file on Omega # TODO: check if running on Omega, otherwise raise an error params.logger.verbose( From ac1803770a869e3e8508816b42ca3b65ef27a86e Mon Sep 17 00:00:00 2001 From: yumouwei <46117079+yumouwei@users.noreply.github.com> Date: Mon, 4 May 2026 09:13:49 -0700 Subject: [PATCH 13/17] close tree & unset bradial_path afterward accessing mds tree --- disruption_py/machine/d3d/physics.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/disruption_py/machine/d3d/physics.py b/disruption_py/machine/d3d/physics.py index 8316814a9..94b205515 100644 --- a/disruption_py/machine/d3d/physics.py +++ b/disruption_py/machine/d3d/physics.py @@ -972,10 +972,13 @@ def get_n1_bradial_parameters(params: PhysicsMethodParams): os.environ["bradial_path"] = config( "d3d" ).physics.get_n1_bradial_parameters.recomputed_tree_path - tree = MDSplus.Tree("bradial", params.shot_id) - n_equal_1_mode = tree.getNode("dusbradial").data().squeeze() # [G] - t_n1 = tree.getNode("dusbradial").dim_of().data() # [ms] - # TODO: unset os.environ? + try: + tree = MDSplus.Tree("bradial", params.shot_id) + n_equal_1_mode = tree.getNode("dusbradial").data().squeeze() # [G] + t_n1 = tree.getNode("dusbradial").dim_of().data() # [ms] + finally: + tree.close() + del os.environ["bradial_path"] elif 176030 <= params.shot_id <= 176912: # Load data from a NetCDF file on Omega # TODO: check if running on Omega, otherwise raise an error From b8b9083737e18e85ad7e2375935678bb56fcfc3d Mon Sep 17 00:00:00 2001 From: yumouwei <46117079+yumouwei@users.noreply.github.com> Date: Mon, 4 May 2026 09:19:38 -0700 Subject: [PATCH 14/17] Shorten config name, update verbose statement --- disruption_py/machine/d3d/config.toml | 6 +++--- disruption_py/machine/d3d/physics.py | 12 +++++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/disruption_py/machine/d3d/config.toml b/disruption_py/machine/d3d/config.toml index 988ccb67b..298f1c4c2 100644 --- a/disruption_py/machine/d3d/config.toml +++ b/disruption_py/machine/d3d/config.toml @@ -10,9 +10,9 @@ driver = "FreeTDS" host = "d3drdb" port = 8001 -[d3d.physics.get_n1_bradial_parameters] -recalc_nc_path = "/fusion/projects/disruption_warning/software/recalc_bradial/recalc.nc" -recomputed_tree_path = "/fusion/projects/disruption_warning/software/recalc_bradial/bradial" +[d3d.physics.bradial_path] +mds_trees = "/fusion/projects/disruption_warning/software/recalc_bradial/bradial" +recalc_nc = "/fusion/projects/disruption_warning/software/recalc_bradial/recalc.nc" [d3d.physics.time_domain_thresholds] dipprog_dt = 2e3 diff --git a/disruption_py/machine/d3d/physics.py b/disruption_py/machine/d3d/physics.py index 94b205515..a7d477815 100644 --- a/disruption_py/machine/d3d/physics.py +++ b/disruption_py/machine/d3d/physics.py @@ -969,9 +969,11 @@ def get_n1_bradial_parameters(params: PhysicsMethodParams): if 156199 <= params.shot_id <= 172430: # Load data from custom tree structures on Omega # TODO: check if running on Omega, otherwise raise an error - os.environ["bradial_path"] = config( - "d3d" - ).physics.get_n1_bradial_parameters.recomputed_tree_path + params.logger.verbose( + "get_n1_bradial_parameters: Retrieving recomputed DUSBRADIAL signal " + "from custom MDSplus trees." + ) + os.environ["bradial_path"] = config("d3d").physics.bradial_path.mds_trees try: tree = MDSplus.Tree("bradial", params.shot_id) n_equal_1_mode = tree.getNode("dusbradial").data().squeeze() # [G] @@ -983,9 +985,9 @@ def get_n1_bradial_parameters(params: PhysicsMethodParams): # Load data from a NetCDF file on Omega # TODO: check if running on Omega, otherwise raise an error params.logger.verbose( - "get_n1_bradial_parameters: Retrieving data from recalc.nc." + "get_n1_bradial_parameters: Retrieving recomputed DUSBRADIAL signal from recalc.nc." ) - filename = config("d3d").physics.get_n1_bradial_parameters.recalc_nc_path + filename = config("d3d").physics.bradial_path.recalc_nc ds = xr.open_dataset(filename) shot_data = ds.sel(shot=params.shot_id) n_equal_1_mode = shot_data["dusbradial_calculated"].values.copy() # [G] From 93742f251584d067e179767cbb2ac8c88aa47744 Mon Sep 17 00:00:00 2001 From: gtrevisan Date: Mon, 4 May 2026 14:40:06 -0400 Subject: [PATCH 15/17] fix MDSplus import --- disruption_py/machine/d3d/physics.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/disruption_py/machine/d3d/physics.py b/disruption_py/machine/d3d/physics.py index a7d477815..e1345afe8 100644 --- a/disruption_py/machine/d3d/physics.py +++ b/disruption_py/machine/d3d/physics.py @@ -6,7 +6,6 @@ import os -import MDSplus import numpy as np import scipy import xarray as xr @@ -22,7 +21,7 @@ matlab_gsastd, matlab_power, ) -from disruption_py.inout.mds import mdsExceptions +from disruption_py.inout.mds import MDSplus, mdsExceptions from disruption_py.machine.d3d.util import D3DUtilMethods from disruption_py.machine.tokamak import Tokamak From ff2b0178def576627c9b36f30bae788a48ba1564 Mon Sep 17 00:00:00 2001 From: yumouwei <46117079+yumouwei@users.noreply.github.com> Date: Tue, 5 May 2026 13:50:36 -0700 Subject: [PATCH 16/17] Minor changes from comments --- disruption_py/machine/d3d/physics.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/disruption_py/machine/d3d/physics.py b/disruption_py/machine/d3d/physics.py index e1345afe8..da0d93688 100644 --- a/disruption_py/machine/d3d/physics.py +++ b/disruption_py/machine/d3d/physics.py @@ -972,9 +972,14 @@ def get_n1_bradial_parameters(params: PhysicsMethodParams): "get_n1_bradial_parameters: Retrieving recomputed DUSBRADIAL signal " "from custom MDSplus trees." ) - os.environ["bradial_path"] = config("d3d").physics.bradial_path.mds_trees + os.environ["bradial_path"] = config( + params.tokamak + ).physics.bradial_path.mds_trees try: tree = MDSplus.Tree("bradial", params.shot_id) + params.logger.debug( + "Opened custom tree: {tree}", tree=tree.getFileName() + ) n_equal_1_mode = tree.getNode("dusbradial").data().squeeze() # [G] t_n1 = tree.getNode("dusbradial").dim_of().data() # [ms] finally: @@ -986,10 +991,10 @@ def get_n1_bradial_parameters(params: PhysicsMethodParams): params.logger.verbose( "get_n1_bradial_parameters: Retrieving recomputed DUSBRADIAL signal from recalc.nc." ) - filename = config("d3d").physics.bradial_path.recalc_nc + filename = config(params.tokamak).physics.bradial_path.recalc_nc ds = xr.open_dataset(filename) shot_data = ds.sel(shot=params.shot_id) - n_equal_1_mode = shot_data["dusbradial_calculated"].values.copy() # [G] + n_equal_1_mode = shot_data["dusbradial_calculated"].values # [G] t_n1 = shot_data["times"].values.copy() # [ms] else: try: From c4bbc399c552b93f5ebd622b7d4bae0e79e48d82 Mon Sep 17 00:00:00 2001 From: yumouwei <46117079+yumouwei@users.noreply.github.com> Date: Tue, 5 May 2026 14:15:43 -0700 Subject: [PATCH 17/17] Add checks for whether bradial_shotno.tree & recalc.nc files exist --- disruption_py/machine/d3d/physics.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/disruption_py/machine/d3d/physics.py b/disruption_py/machine/d3d/physics.py index da0d93688..303acf128 100644 --- a/disruption_py/machine/d3d/physics.py +++ b/disruption_py/machine/d3d/physics.py @@ -967,14 +967,18 @@ def get_n1_bradial_parameters(params: PhysicsMethodParams): # must be loaded from a separate datafile if 156199 <= params.shot_id <= 172430: # Load data from custom tree structures on Omega - # TODO: check if running on Omega, otherwise raise an error params.logger.verbose( "get_n1_bradial_parameters: Retrieving recomputed DUSBRADIAL signal " "from custom MDSplus trees." ) - os.environ["bradial_path"] = config( - params.tokamak - ).physics.bradial_path.mds_trees + # Check if the custom bradial tree exists + bradial_path = config(params.tokamak).physics.bradial_path.mds_trees + tree_path = os.path.join(bradial_path, f"bradial_{params.shot_id}.tree") + if not os.path.exists(tree_path): + raise FileNotFoundError( + f"custom MDSplus tree does not exist: {tree_path}" + ) + os.environ["bradial_path"] = bradial_path try: tree = MDSplus.Tree("bradial", params.shot_id) params.logger.debug( @@ -987,11 +991,13 @@ def get_n1_bradial_parameters(params: PhysicsMethodParams): del os.environ["bradial_path"] elif 176030 <= params.shot_id <= 176912: # Load data from a NetCDF file on Omega - # TODO: check if running on Omega, otherwise raise an error params.logger.verbose( "get_n1_bradial_parameters: Retrieving recomputed DUSBRADIAL signal from recalc.nc." ) + # Check if the NetCDF file exists filename = config(params.tokamak).physics.bradial_path.recalc_nc + if not os.path.exists(filename): + raise FileNotFoundError(f"recalc.nc file does not exist: {filename}") ds = xr.open_dataset(filename) shot_data = ds.sel(shot=params.shot_id) n_equal_1_mode = shot_data["dusbradial_calculated"].values # [G]