Add three new C-Mod physics methods with H-mode related variables#562
Add three new C-Mod physics methods with H-mode related variables#562zapatace wants to merge 8 commits into
Conversation
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
gtrevisan
left a comment
There was a problem hiding this comment.
thanks, Enrique! it looks great.
I've left some minor comments after a quick look, I'll review again in the next few days.
| [cmod.physics.attributes.h_alpha] | ||
| description = "H-alpha line emission intensity." | ||
| units = "W/(m2*sr)" | ||
| validity = [0.0, 161.920898] |
There was a problem hiding this comment.
There may be a way to use the spectrometer_visible category for this. I'm uncertain.
There was a problem hiding this comment.
We can use
[cmod.physics.attributes.h_alpha]
description = "H-alpha line emission intensity."
imas = "/spectrometer_visible/channel(i1)/grating_spectrometer/processed_line(i2)/radiance"
units = "W/(m^2*sr)"
validity = [0.0, 161.920898]
However, IMAS units are photons·m⁻²·s⁻¹·sr⁻¹ Do you want to use these units in the method (they are not SI)?
| [cmod.physics.attributes.pow_thr_LH_Martin] | ||
| description = "Martin 2008 L-H transition power threshold scaling." | ||
| units = "W" | ||
| validity = [0, 0.1e8] |
There was a problem hiding this comment.
I did not find a related IMAS entry.
There was a problem hiding this comment.
Me neither. The only L–H-related IDSs are global_quantities/h_mode (confinement state label) and global_quantities/power_loss (actual P_sep), neither of which is the Martin-scaling threshold.
| ) # tmag: [s] | ||
| # Toroidal power supply takes time to turn on, from ~ -1.8 and should be | ||
| # on by t=-1. So pick the time before that to calculate baseline | ||
| baseline_indices = np.where(t_mag <= -1.8) |
There was a problem hiding this comment.
don't you need a comma, here? please double check dimensions.
your slicing might work in the following line, but this might not be what you want -- a list of indices.
There was a problem hiding this comment.
I believe this line is ok.
np.where() returns indices if no optional array arguments are provided.
I also checked the dimensions, but please confirm that it behaves as intended.
There was a problem hiding this comment.
This is interesting. It looks like arr[idx] automatically unpacks the tuple, so btor[baseline_indices] automatically becomes btor[baseline_indices[0]]. I actually didn't know this, which makes me wonder why we chose to do idx, = np.where(...) in the first place...
There was a problem hiding this comment.
You can see that behaviour is the same with this code snippet.
import numpy as np
t_mag = np.linspace(-2.0, 0.0, 11)
btor = np.arange(11, dtype=float)
baseline_indices = np.where(t_mag <= -1.8) # tuple -> (array([0, 1]),)
idx, = np.where(t_mag <= -1.8) # comma idiom -> array([0, 1])
np.array_equal(btor[baseline_indices], btor[idx]) # True
np.mean(btor[baseline_indices]) == np.mean(btor[idx]) # True (both 0.5)
| # Get BT | ||
| btor, t_mag = params.data_conn.get_data_with_dims( | ||
| r"\btor", group="magnetics" | ||
| ) # tmag: [s] |
| ) # tmag: [s] | ||
| # Toroidal power supply takes time to turn on, from ~ -1.8 and should be | ||
| # on by t=-1. So pick the time before that to calculate baseline | ||
| baseline_indices = np.where(t_mag <= -1.8) |
There was a problem hiding this comment.
Pull request overview
This PR adds three new CMOD physics retrieval methods and registers their metadata (descriptions/units/validity ranges) in the CMOD config, targeting H-mode related quantities for downstream analysis.
Changes:
- Add
get_h_alphato retrieve and interpolate H-alpha brightness ontoparams.times. - Add
get_h98to compute the H98 confinement enhancement factor from EFIT, density, power, and magnetics signals. - Add
get_itpa_pow_thrto compute the Martin (2008) L–H power threshold scaling and expose it aspow_thr_LH_Martin, plus new attribute entries inconfig.toml.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
disruption_py/machine/cmod/physics.py |
Adds the three new physics methods (get_h_alpha, get_h98, get_itpa_pow_thr). |
disruption_py/machine/cmod/config.toml |
Registers new attribute metadata for h98, h_alpha, and pow_thr_LH_Martin. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ip = np.abs(ip_df.get("ip")) / 1.0e6 # [A] -> [MA] | ||
| n_e = density_df.get("n_e") / 1.0e19 # [m^-3] -> [10^19 m^-3] | ||
| p_input = powers_df.get("p_input") / 1.0e6 # [W] -> [MW] | ||
| dwmhd_dt = efit_df.get("dwmhd_dt") / 1.0e6 # [W] -> [MW] |
There was a problem hiding this comment.
A significant portion of the data used to compute the IPB98(y,2) scaling were published in the second version of the H mode confinement database ITERHDB.2 (DB2). An ITER H Mode Database Working Group paper detailing updates to this database was published in Nuclear Fusion in 1994. The units listed for line averaged density are 10^19 m^-3, and the paper lists two scaling laws for energy confinement time.
For reference, the IPB98(y,2) scaling can be found in Table 5 of ITER Physics Expert Group on Confinement and Transport et al 1999 Nucl. Fusion 39 2175. This paper also lists 10^19 m^-3 as units of density.
I'll leave this thread open in case a reviewer wants to take a second look.
There was a problem hiding this comment.
I remember wrestling with this. Thank you, @nbarbour13, for tracking down the references again! The papers state it pretty clearly. This H98 scaling is formulated with cental line averaged densities in [10¹⁹ m⁻³]. Here is some more evidence: updated ITPA DB. Copilot is hallucinating here. While it is true that in power threshold scaling the density is [10²⁰ m⁻³], this is not the case in H98.
| # Get BT | ||
| btor, t_mag = params.data_conn.get_data_with_dims( | ||
| r"\btor", group="magnetics" | ||
| ) # tmag: [s] | ||
| # Toroidal power supply takes time to turn on, from ~ -1.8 and should be | ||
| # on by t=-1. So pick the time before that to calculate baseline | ||
| baseline_indices = np.where(t_mag <= -1.8) | ||
| btor = btor - np.mean(btor[baseline_indices]) | ||
| btor = np.abs(interp1(t_mag, btor, params.times)) # [T] |
There was a problem hiding this comment.
We might want to distill the btor computation in get_n_equal_1_amplitude, get_h98 and get_itpa_pow_thr into a single physics method similar to D3DPhysicsMethods.get_btor, or just call get_n_equal_1_amplitude to get the btor signal.
I do realize this suggestion is contradictory with my suggestion in R2180 so take this with a grain of salt.
There was a problem hiding this comment.
I can make a helper function for the 3 methods. However, since I need to modify get_n_equal_1_amplitude can we leave this for a follow-up small PR? Up to you!
This PR adds three new physics methods to
disruption_py/machine/cmod/physics.py, along with their attribute definitions (description, units, validity limits) indisruption_py/machine/cmod/config.toml.h98get_h98[0.05, 1.75]h_alphaget_h_alpha[0.0, 161.920898]pow_thr_LH_Martinget_itpa_pow_thr[0, 0.1e8]I used the following shot as reference example.

In this example, there are 6 alternating LH transitions followed by HL transition. Notice how H98 approach one, while H-alpha diminish just after Pinput -Prad crosses P_thr from Martin scaling (although not always).
Validation / error analysis
I run the workflow on scale using disruption warning table. I also run disruption-errors script and no tracebacks or errors originate from these 3 methods, 99.99% of fata was retrieved at ~0.015s per shot.
Next, we have the definition and value distributions for the 3 methods on the disruption warning table.
get_h_alpha→h_alphaH-alpha line emission intensity, useful as a marker of ELMs, radiative events, and confinement-regime transitions. Reads
\SPECTROSCOPY::HA_2_BRIGHT[mW/(cm²·sr)] and interpolates ontoparams.times, converting to SI brightness unitsW/(m²·sr). Falls back to NaNs (with a warning) if the signal is unavailable. Be adviced that for ELM detection the native signal time base is necessary to avoid losing fast transient events.Multimodal distribution with median ≈ 14 W/(m²·sr), IQR ≈ [5.7, 32]. Some shots show small negative values (min ≈ −104), and there are discrete spikes/saturation lines at
80.523056and161.920898— the latter is used as the upper validity bound.Negative and saturated shots were inspected, here you have two examples
I checked with Bob Granetz, and all of this appears to be a unknown diagnostic errors (like wrong offset or maybe some gain adjust that caused the distortion).
get_h98→h98H98 energy-confinement enhancement factor,
h98 = τ_E / τ_98, where the measured confinement time isτ_E = W_mhd / (P_input − dW_mhd/dt)andτ_98is the ITER IPB98(y,2) scaling (eq. 20, ITER Physics Basis Ch. 2), evaluated with atomic mass A = 2. Non-physical non-positive values are clipped to 0.Skewed Gaussian peaked around ~0.45 with a long tail toward 1.0 and a large spike at 0 (clipped non-positive values). Raw stats: median ≈ 0.45, IQR ≈ [0.32, 0.57];
mean/maxareinfbecauseP_input − dW_mhd/dt → 0produces divide-by-zero spikes. The[0.05, 1.75]validity window in config.toml filters both the zero spike and theinfoutliers.get_itpa_pow_thr→pow_thr_LH_MartinL–H transition power threshold from the Martin 2008 scaling (Y. R. Martin et al 2008 J. Phys.: Conf. Ser. 123 012033):
with
n_ein 10²⁰ m⁻³ (get_densities),B_tthe baseline-subtracted absolute toroidal field, andSthe plasma surface area fromget_kappa_area. Thenp.sign(x)·|x|^pform guards against complex results from negative inputs (check the code).Roughly log-normal distribution, peaked near ~0.28 MW with median ≈ 2.86e5 W and IQR ≈ [2.12e5, 3.64e5] W. A small number of unphysical outliers exist (min ≈ −2.4e7, max ≈ 1.7e8 W) driven by extreme density/area inputs; the
[0, 0.1e8]validity range (to be removed by the user).Warnings
h98producesinfwhenP_input ≈ dW_mhd/dt; relies on user postprocessing.h_alphacan be negative and saturates at discrete values on some shots.