Process reference: confinement
Generated from the docstrings of vaft.process.confinement:
18 public functions. Each entry is what
vaft.process.describe("confinement.<name>") prints.
All process categories
Overview
The first layer of the #548 hierarchy: from time series of plasma current,
resistive loop voltage and stored energy to the loss power and confinement
time of each time slice, and the evidence that decides whether a slice enters
a scaling fit. It composes the definitions of vaft.formula.equilibrium
and adds no physics of its own.
Two loss powers are kept apart, because databases do not agree on one:
================= =========================================== =====================
name definition compares with
================= =========================================== =====================
p_net $P_\mathrm{OH} - dW/dt$ ITPA DB5 PLTH
p_transport $P_\mathrm{OH} - dW/dt - P_\mathrm{rad}$ transport analyses
================= =========================================== =====================
p_transport is NaN wherever the radiated power was not measured; a
modelled $P_\mathrm{rad}$ is a sensitivity study, never the default.
Slice qualification keeps the evidence (rates and ratios per slice) apart
from the decision (thresholds), as #253 asks: thresholds are arguments,
never constants copied from another database, and
confinement_exclusion_table reports how many slices each rule
removes so a threshold sweep can be read off directly.
Functions
assess_predictor_identifiability | Collinearity of log predictors: correlation, VIF, condition number and rank. |
bootstrap_confinement_scaling | Group (shot) bootstrap of the log-linear scaling coefficients. |
closure_constraint | Linear constraint on I, B, P (and n) exponents fixing the Kadomtsev-completed $\mu_\rho$. |
confinement_exclusion_table | How many slices each quality rule removes, alone and in sequence. |
confinement_power_balance | Ohmic heating, stored-energy change, loss powers and confinement times of a discharge. |
confinement_slice_decision | Pass/fail of each slice-quality rule, and their conjunction, for given thresholds. |
confinement_slice_evidence | Rates and ratios that decide whether a time slice is quasi-stationary enough to fit. |
dimensionless_confinement_indices | Kadomtsev-completed dimensionless indices $(\mu_\rho, \mu_\beta, \mu_\nu, \mu_q)$ with their covariance. |
fit_confinement_scaling | Log-linear power-law fit $y = C \prod_j x_j^{\alpha_j}$ with errors clustered by group. |
fit_confinement_scaling_odr | Errors-in-variables (orthogonal distance) power-law fit in log space. |
fit_constrained_confinement_scaling | Log-linear power-law fit under one linear equality constraint on the exponents. |
infer_kadomtsev_size_exponent | Size exponent the Kadomtsev constraint implies for fitted I, B, P (and n) exponents. |
leave_one_group_out_scaling | Leave-one-group-out refits: coefficient stability and out-of-group prediction error. |
ohmic_confinement_series | Ohmic power balance and confinement time from a loop voltage, the internal inductance and a stored energy. |
predictor_principal_directions | Principal directions of the log predictors: which exponent combinations the data constrain. |
resistive_loop_voltage | Inductive and resistive parts of a loop voltage, and the plasma resistance, from the internal inductance. |
resolve_observed_confinement | Observed confinement time with the energy basis a scaling was fitted on (issue #1713). |
smoothed_time_derivative | Time derivative of a sampled quantity by a local polynomial fit, on any time grid. |
No function matches.
assess_predictor_identifiability
Machine-independent
source
assess_predictor_identifiability(predictors)
Collinearity of log predictors: correlation, VIF, condition number and rank.
| Name | Type | Unit | Description |
|---|---|---|---|
predictors | Mapping | any | Predictor arrays keyed by name; rows with any non-finite or non-positive value are dropped. |
| Returns | |||
| — | dict | any |
|
Raises
ValueErrorNo predictor, or mismatched lengths.
Applicability · Limitations · Provenance
Applicability
Machine-independent.
Limitations
Says whether the sample can separate the exponents, not whether a fit did; a predictor that barely varies has a large VIF only if it is also correlated with another, so read the spread of each log predictor too. The condition number follows Belsley (columns scaled to unit length, intercept kept); values above about 30 signal harmful collinearity.
Provenance
- Issue #548 Sec. 3 and 6: B_T stays a fitted variable only if its covariance with I_p and P is quantified.
- D. A. Belsley, E. Kuh and R. E. Welsch, Regression Diagnostics, Wiley (1980), Ch. 3.
bootstrap_confinement_scaling
Machine-independent
source
bootstrap_confinement_scaling(response, predictors, groups, *, n_boot=2000, seed=0)
Group (shot) bootstrap of the log-linear scaling coefficients.
| Name | Type | Unit | Description |
|---|---|---|---|
response | numpy.ndarray | any | The fitted quantity. |
predictors | Mapping | any | Predictor arrays keyed by name. |
groups | numpy.ndarray | - | Group label per row; whole groups are resampled. |
n_boot | int, optional | - | Number of resamples. |
seed | int, optional | - | Seed of the random generator. |
| Returns | |||
| — | dict | any |
|
Raises
ValueErrorAsfit_confinement_scalingfor the full sample.
Defaults · Applicability · Limitations · Provenance
Defaults
n_boot = 2000 and seed = 0: numerical convenience; 2000 resamples
put the percentile endpoints within about a percent of their limit.
Applicability
Machine-independent.
Limitations
With a handful of groups the bootstrap distribution is lumpy and its
intervals undercover; rejected counts resamples that could not
identify every exponent (e.g. a single field value drawn), which is
itself identifiability evidence.
Provenance
- Issue #548 Sec. 6: slices of one shot are not independent, so resampling is by shot.
- B. Efron and R. J. Tibshirani, An Introduction to the Bootstrap, Chapman & Hall (1993), Ch. 13.
closure_constraint
Machine-independent
source
closure_constraint(mu_rho, names)
Show source vaft/process/confinement.py, lines 1776–1831
def closure_constraint(mu_rho: float, names) -> tuple[dict, float]:
"""Linear constraint on I, B, P (and n) exponents fixing the Kadomtsev-completed $\\mu_\\rho$.
Parameters
----------
mu_rho : float
Gyroradius index of the closure: $-2$ Bohm, $-3$ gyro-Bohm, or any
value between [-].
names : sequence of str
The fit's predictor names: ``i_p``, ``b_t``, ``p_net`` and optionally
``n_e`` [-].
Returns
-------
coef : dict
Constraint coefficient per predictor name [-].
rhs : float
Right-hand side, so that ``sum(coef[k] * alpha[k]) == rhs`` [-].
Raises
------
ValueError
Unknown or missing predictor names.
Processing steps
----------------
1. Complete the size exponent with the Kadomtsev constraint,
$\\alpha_R = (\\alpha_I + 5\\alpha_B + 3\\alpha_P + 8\\alpha_n + 5)/4$.
2. Substitute into
$\\mu_\\rho(1+\\alpha_P) = \\alpha_B - \\alpha_I - 2\\alpha_R + 2\\alpha_n
- 3\\alpha_P + 1$, which leaves
$\\tfrac32\\alpha_I + \\tfrac32\\alpha_B + (\\mu_\\rho + \\tfrac92)\\alpha_P
+ 2\\alpha_n = -(\\mu_\\rho + \\tfrac32)$.
Applicability
-------------
Machine-independent.
Limitations
-----------
The closures are alternative hypotheses, not simultaneous constraints;
the intermediate $\\mu_\\rho = -2.5$ is a single index, not an additive
Bohm plus gyro-Bohm model (#548 Sec. 8). A density-free fit has
$\\alpha_n = 0$ by definition, which is part of the hypothesis tested.
Provenance
----------
.. [548] Issue #548 Sec. 8: Bohm, intermediate and gyro-Bohm closures as
one parameterized constraint.
"""
allowed = {"i_p": 1.5, "b_t": 1.5, "p_net": float(mu_rho) + 4.5, "n_e": 2.0}
names = list(names)
unknown = [name for name in names if name not in allowed]
if unknown or not {"i_p", "b_t", "p_net"} <= set(names):
raise ValueError(f"predictors must be i_p, b_t, p_net and optionally n_e; got {names}")
return {name: allowed[name] for name in names}, -(float(mu_rho) + 1.5)Linear constraint on I, B, P (and n) exponents fixing the Kadomtsev-completed $\mu_\rho$.
| Name | Type | Unit | Description |
|---|---|---|---|
mu_rho | float | - | Gyroradius index of the closure: $-2$ Bohm, $-3$ gyro-Bohm, or any value between. |
names | sequence of str | - | The fit’s predictor names: |
| Returns | |||
coef | dict | - | Constraint coefficient per predictor name. |
rhs | float | - | Right-hand side, so that |
Raises
ValueErrorUnknown or missing predictor names.
Processing steps · Applicability · Limitations · Provenance
Processing steps
- Complete the size exponent with the Kadomtsev constraint, $\alpha_R = (\alpha_I + 5\alpha_B + 3\alpha_P + 8\alpha_n + 5)/4$.
- Substitute into
$\mu_\rho(1+\alpha_P) = \alpha_B - \alpha_I - 2\alpha_R + 2\alpha_n
- 3\alpha_P + 1$, which leaves $\tfrac32\alpha_I + \tfrac32\alpha_B + (\mu_\rho + \tfrac92)\alpha_P
- 2\alpha_n = -(\mu_\rho + \tfrac32)$.
Applicability
Machine-independent.
Limitations
The closures are alternative hypotheses, not simultaneous constraints; the intermediate $\mu_\rho = -2.5$ is a single index, not an additive Bohm plus gyro-Bohm model (#548 Sec. 8). A density-free fit has $\alpha_n = 0$ by definition, which is part of the hypothesis tested.
Provenance
- Issue #548 Sec. 8: Bohm, intermediate and gyro-Bohm closures as one parameterized constraint.
confinement_exclusion_table
Machine-independent
source
confinement_exclusion_table(rules)
Show source vaft/process/confinement.py, lines 790–843
def confinement_exclusion_table(rules: Mapping[str, np.ndarray]) -> list[dict]:
"""How many slices each quality rule removes, alone and in sequence.
Parameters
----------
rules : Mapping
Boolean pass arrays keyed by rule name, in the order to apply them,
as :func:`confinement_slice_decision` returns; an ``accepted`` key
is ignored [bool].
Returns
-------
list of dict
One row per rule: ``rule``, ``failed`` (slices failing it),
``failed_only_this`` (failing this rule and no other),
``removed_in_sequence`` (newly removed when the rules are applied in
order) and ``remaining`` (after it) [-].
Raises
------
ValueError
Pass arrays of different length.
Applicability
-------------
Machine-independent.
Provenance
----------
.. [548] Issue #548 Sec. 5: the rules and the number each excludes are
part of the result.
"""
names = [name for name in rules if name != "accepted"]
arrays = [np.asarray(rules[name], dtype=bool) for name in names]
if len({a.size for a in arrays}) > 1:
raise ValueError("every rule must judge the same slices")
if not arrays:
return []
passing = np.ones(arrays[0].size, dtype=bool)
stack = np.vstack(arrays)
table = []
for k, (name, passed) in enumerate(zip(names, arrays)):
others = np.delete(stack, k, axis=0)
others_pass = others.all(axis=0) if others.size else np.ones_like(passed)
removed = passing & ~passed
passing = passing & passed
table.append({
"rule": name,
"failed": int(np.sum(~passed)),
"failed_only_this": int(np.sum(~passed & others_pass)),
"removed_in_sequence": int(np.sum(removed)),
"remaining": int(np.sum(passing)),
})
return tableHow many slices each quality rule removes, alone and in sequence.
| Name | Type | Unit | Description |
|---|---|---|---|
rules | Mapping | bool | Boolean pass arrays keyed by rule name, in the order to apply them, as |
| Returns | |||
| — | list of dict | - | One row per rule: |
Raises
ValueErrorPass arrays of different length.
Applicability · Provenance
Applicability
Machine-independent.
Provenance
- Issue #548 Sec. 5: the rules and the number each excludes are part of the result.
confinement_power_balance
Machine-independent
source
confinement_power_balance(time, i_p, v_res, w_th, *, p_rad=None, dwdt_window_s=None, dwdt_polyorder=2)
Ohmic heating, stored-energy change, loss powers and confinement times of a discharge.
| Name | Type | Unit | Description |
|---|---|---|---|
time | numpy.ndarray | s | Sample times, strictly increasing, 1-D. |
i_p | numpy.ndarray | A | Plasma current, in the orientation of |
v_res | numpy.ndarray | V | Resistive loop voltage, the loop voltage with the inductive part removed. |
w_th | numpy.ndarray | J | Stored (thermal) energy whose change and confinement are wanted. |
p_rad | numpy.ndarray, optional | W | Measured radiated power; |
dwdt_window_s | float, optional | s | Window of the local-polynomial $dW/dt$ derivative, see |
dwdt_polyorder | int, optional | - | Polynomial order of that derivative. |
| Returns | |||
| — | ConfinementPowerBalance | any |
|
Raises
ValueErrorArrays of different length, or whatsmoothed_time_derivativerejects.
Processing steps · Applicability · Limitations · Provenance
Processing steps
- $P_\mathrm{OH} = I_p V_\mathrm{res}$
(
vaft.formula.equilibrium.ohmic_heating_power_from_I_p_V_res). - $dW/dt$ by
smoothed_time_derivativeonw_th. - $P_\mathrm{net} = P_\mathrm{OH} - dW/dt$ and, where
p_radis finite, $P_\mathrm{transport} = P_\mathrm{net} - P_\mathrm{rad}$ (vaft.formula.equilibrium.loss_power_from_p_heat_dWdt_p_rad). - $\tau_E = W/P$ for each loss power, NaN where that power is not positive.
Applicability
Machine-independent. Ohmic plasmas: no auxiliary heating term.
Limitations
v_res carries every assumption of how the inductive voltage was
removed; with a loop voltage taken away from the plasma boundary the
flux between loop and boundary is still inductive. The transport loss
power needs a measured radiated power, which VEST does not map
(no bolometer), so on VEST it is NaN.
Provenance
- Issue #548 Sec. 1: P_net and P_transport as separate, named quantities; W = (3/2) int p dV fixed by #1282/#1410.
- ITER Physics Expert Groups, Nucl. Fusion 39 (1999) 2175, Ch. 2: the loss power $P_L = P_\mathrm{heat} - dW/dt$ of the confinement database.
confinement_slice_decision
Machine-independent
source
confinement_slice_decision(evidence, *, ip_min, max_dwdt_fraction, max_ip_change_per_tau)
Show source vaft/process/confinement.py, lines 730–787
def confinement_slice_decision(
evidence: ConfinementSliceEvidence,
*,
ip_min: float,
max_dwdt_fraction: float,
max_ip_change_per_tau: float,
) -> dict[str, np.ndarray]:
"""Pass/fail of each slice-quality rule, and their conjunction, for given thresholds.
Parameters
----------
evidence : ConfinementSliceEvidence
Output of :func:`confinement_slice_evidence` [any].
ip_min : float
Smallest accepted $|I_p|$ [A].
max_dwdt_fraction : float
Largest accepted $|dW/dt|/P_\\mathrm{OH}$ [-].
max_ip_change_per_tau : float
Largest accepted fractional current change within one confinement
time, $\\tau_E |dI_p/dt|/|I_p|$ [-].
Returns
-------
dict
Boolean arrays keyed ``finite``, ``ip_min``, ``dwdt_fraction``,
``ip_change_per_tau`` and ``accepted`` (all rules); a NaN evidence
value fails its rule [bool].
Applicability
-------------
Machine-independent. The thresholds are the caller's: none is a
database or machine default.
Limitations
-----------
Ramp phases are judged by rate against the confinement time, not by a
phase label: a short discharge with no flat top (VEST) has no plateau to
select, and a rate rule says how far from stationary each slice is.
Provenance
----------
.. [548] Issue #548 Sec. 5: thresholds as arguments, studied by sweep
before any is adopted.
"""
ev = evidence
rules = {
"finite": np.asarray(ev.finite, dtype=bool),
"ip_min": np.nan_to_num(ev.ip_abs, nan=-np.inf) >= float(ip_min),
"dwdt_fraction": np.nan_to_num(ev.dwdt_fraction, nan=np.inf) <= float(max_dwdt_fraction),
"ip_change_per_tau": (
np.nan_to_num(ev.ip_change_per_tau, nan=np.inf) <= float(max_ip_change_per_tau)
),
}
accepted = np.ones_like(rules["finite"])
for passed in rules.values():
accepted = accepted & passed
rules["accepted"] = accepted
return rulesPass/fail of each slice-quality rule, and their conjunction, for given thresholds.
| Name | Type | Unit | Description | |||||
|---|---|---|---|---|---|---|---|---|
evidence | ConfinementSliceEvidence | any | Output of | |||||
ip_min | float | A |
| |||||
max_dwdt_fraction | float | - |
| |||||
max_ip_change_per_tau | float | - |
| |||||
| Returns | ||||||||
| — | dict | bool | Boolean arrays keyed | |||||
Applicability · Limitations · Provenance
Applicability
Machine-independent. The thresholds are the caller’s: none is a database or machine default.
Limitations
Ramp phases are judged by rate against the confinement time, not by a phase label: a short discharge with no flat top (VEST) has no plateau to select, and a rate rule says how far from stationary each slice is.
Provenance
- Issue #548 Sec. 5: thresholds as arguments, studied by sweep before any is adopted.
confinement_slice_evidence
Machine-independent
source
confinement_slice_evidence(i_p, dip_dt, w_th, p_ohmic, dwdt, tau_e)
Show source vaft/process/confinement.py, lines 664–727
def confinement_slice_evidence(
i_p: np.ndarray,
dip_dt: np.ndarray,
w_th: np.ndarray,
p_ohmic: np.ndarray,
dwdt: np.ndarray,
tau_e: np.ndarray,
) -> ConfinementSliceEvidence:
"""Rates and ratios that decide whether a time slice is quasi-stationary enough to fit.
Parameters
----------
i_p : numpy.ndarray
Plasma current at the slices [A].
dip_dt : numpy.ndarray
Its time derivative at the slices [A/s].
w_th : numpy.ndarray
Stored energy at the slices [J].
p_ohmic : numpy.ndarray
Ohmic heating power at the slices [W].
dwdt : numpy.ndarray
Stored-energy change at the slices [W].
tau_e : numpy.ndarray
Confinement time at the slices [s].
Returns
-------
ConfinementSliceEvidence
``ip_abs`` [A], ``ip_rate`` [1/s], ``ip_change_per_tau`` and
``dwdt_fraction`` [-], ``finite`` [bool] [any].
Raises
------
ValueError
Arrays of different length.
Applicability
-------------
Machine-independent.
Provenance
----------
.. [548] Issue #548 Sec. 5: the slice-quality evidence, kept apart from
the acceptance decision as #253 asks.
"""
ip = _series("i_p", i_p)
n = ip.size
didt = _series("dip_dt", dip_dt, n)
w = _series("w_th", w_th, n)
p = _series("p_ohmic", p_ohmic, n)
dw = _series("dwdt", dwdt, n)
tau = _series("tau_e", tau_e, n)
ip_abs = np.abs(ip)
with np.errstate(divide="ignore", invalid="ignore"):
ip_rate = np.abs(didt) / ip_abs
dw_frac = np.abs(dw) / p
dw_frac = np.where(p > 0, dw_frac, np.nan)
finite = (
np.isfinite(w) & (w > 0) & np.isfinite(p) & (p > 0) & np.isfinite(tau) & (tau > 0)
)
return ConfinementSliceEvidence(
ip_abs=ip_abs, ip_rate=ip_rate, ip_change_per_tau=ip_rate * tau,
dwdt_fraction=dw_frac, finite=finite,
)Rates and ratios that decide whether a time slice is quasi-stationary enough to fit.
| Name | Type | Unit | Description |
|---|---|---|---|
i_p | numpy.ndarray | A | Plasma current at the slices. |
dip_dt | numpy.ndarray | A/s | Its time derivative at the slices. |
w_th | numpy.ndarray | J | Stored energy at the slices. |
p_ohmic | numpy.ndarray | W | Ohmic heating power at the slices. |
dwdt | numpy.ndarray | W | Stored-energy change at the slices. |
tau_e | numpy.ndarray | s | Confinement time at the slices. |
| Returns | |||
| — | ConfinementSliceEvidence | any |
|
Raises
ValueErrorArrays of different length.
Applicability · Provenance
Applicability
Machine-independent.
Provenance
- Issue #548 Sec. 5: the slice-quality evidence, kept apart from the acceptance decision as #253 asks.
dimensionless_confinement_indices
Machine-independent
source
dimensionless_confinement_indices(alpha, cov)
Show source vaft/process/confinement.py, lines 1694–1773
def dimensionless_confinement_indices(
alpha: Mapping[str, float],
cov: np.ndarray,
) -> dict:
"""Kadomtsev-completed dimensionless indices $(\\mu_\\rho, \\mu_\\beta, \\mu_\\nu, \\mu_q)$ with their covariance.
Parameters
----------
alpha : Mapping
Fitted exponents keyed ``i_p``, ``b_t``, ``p_net`` and optionally
``n_e`` [-].
cov : numpy.ndarray
Their covariance in the order of ``alpha`` [-].
Returns
-------
dict
``alpha_R`` and ``alpha_R_stderr``, the completed size exponent [-];
``names`` (``mu_rho``, ``mu_beta``, ``mu_nu``, ``mu_q``) [-];
``value`` and ``cov`` of the indices [-]; ``stderr`` [-] [any].
Raises
------
ValueError
As :func:`infer_kadomtsev_size_exponent`, or $\\alpha_P = -1$.
Processing steps
----------------
1. Complete the size exponent with :func:`infer_kadomtsev_size_exponent`.
2. Map the completed engineering exponents to dimensionless indices with
:func:`vaft.formula.equilibrium.dimensionless_scaling_coeffs_from_engineering_scaling_coeffs`
(exact here: a completed scaling satisfies the constraint), and
$\\mu_q = -\\alpha_I/(1+\\alpha_P)$.
3. Propagate ``cov`` through the map's Jacobian (central differences,
the size exponent completed at every evaluation).
Applicability
-------------
Machine-independent.
Limitations
-----------
Inherits the *assumed* size exponent: the indices are those of the
Kadomtsev-completed scaling, not measured dimensionless dependences. The
linear propagation is poor when $1 + \\alpha_P$ is within a few standard
errors of zero, where every index diverges.
Provenance
----------
.. [548] Issue #548 Sec. 7 (layer E): dimensionless conversion with
uncertainties and covariances.
.. [Luce] T. C. Luce, C. C. Petty and J. G. Cordey, *Plasma Phys. Control.
Fusion* **50** (2008) 043001.
"""
from vaft.formula.equilibrium import (
dimensionless_scaling_coeffs_from_engineering_scaling_coeffs as _to_dimensionless,
)
names = list(alpha)
a0 = np.array([float(alpha[name]) for name in names])
alpha_r, alpha_r_se = infer_kadomtsev_size_exponent(alpha, cov)
def indices(vec):
a = dict(zip(names, vec))
a_r, _ = infer_kadomtsev_size_exponent(a, np.zeros((len(names), len(names))))
mu = _to_dimensionless(a["i_p"], a["b_t"], a["p_net"], a.get("n_e", 0.0), 0.0, a_r, 0.0, 0.0)
return np.array([mu[0], mu[1], mu[2], -a["i_p"] / (1.0 + a["p_net"])])
value = indices(a0)
jac = np.zeros((4, len(names)))
for j in range(len(names)):
h = 1e-6 * max(1.0, abs(a0[j]))
up, down = a0.copy(), a0.copy()
up[j] += h
down[j] -= h
jac[:, j] = (indices(up) - indices(down)) / (2 * h)
c = jac @ np.atleast_2d(np.asarray(cov, dtype=float)) @ jac.T
return {"alpha_R": alpha_r, "alpha_R_stderr": alpha_r_se,
"names": ("mu_rho", "mu_beta", "mu_nu", "mu_q"), "value": value, "cov": c,
"stderr": np.sqrt(np.clip(np.diag(c), 0.0, None))}Kadomtsev-completed dimensionless indices $(\mu_\rho, \mu_\beta, \mu_\nu, \mu_q)$ with their covariance.
| Name | Type | Unit | Description |
|---|---|---|---|
alpha | Mapping | - | Fitted exponents keyed |
cov | numpy.ndarray | - | Their covariance in the order of |
| Returns | |||
| — | dict | any |
|
Raises
ValueErrorAsinfer_kadomtsev_size_exponent, or $\alpha_P = -1$.
Processing steps · Applicability · Limitations · Provenance
Processing steps
- Complete the size exponent with
infer_kadomtsev_size_exponent. - Map the completed engineering exponents to dimensionless indices with
vaft.formula.equilibrium.dimensionless_scaling_coeffs_from_engineering_scaling_coeffs(exact here: a completed scaling satisfies the constraint), and $\mu_q = -\alpha_I/(1+\alpha_P)$. - Propagate
covthrough the map’s Jacobian (central differences, the size exponent completed at every evaluation).
Applicability
Machine-independent.
Limitations
Inherits the assumed size exponent: the indices are those of the Kadomtsev-completed scaling, not measured dimensionless dependences. The linear propagation is poor when $1 + \alpha_P$ is within a few standard errors of zero, where every index diverges.
Provenance
- Issue #548 Sec. 7 (layer E): dimensionless conversion with uncertainties and covariances.
- T. C. Luce, C. C. Petty and J. G. Cordey, Plasma Phys. Control. Fusion 50 (2008) 043001.
fit_confinement_scaling
Machine-independent
source
fit_confinement_scaling(response, predictors, groups, *, robust=False)
Log-linear power-law fit $y = C \prod_j x_j^{\alpha_j}$ with errors clustered by group.
| Name | Type | Unit | Description |
|---|---|---|---|
response | numpy.ndarray | any | The fitted quantity, usually $\tau_E$ or $W$. |
predictors | Mapping | any | Predictor arrays keyed by name, same length as |
groups | numpy.ndarray | - | Group label per row, normally the shot: rows of one group are not independent samples. |
robust | bool, optional | bool | Huber M-estimation instead of least squares for the coefficients |
| Returns | |||
| — | ConfinementScalingFit | any | Coefficients in log space (exponents, and $\ln C$ first) [-], their cluster-robust and naive errors, 95 % intervals, $R^2$ and the RMS log residual [-], leverage and Cook’s distance per kept row [-]. |
Raises
ValueErrorNo predictor, mismatched lengths, fewer kept rows than coefficients plus one, fewer than two groups, a predictor that does not vary over the kept rows, or an exactly collinear design: a least-squares solver would otherwise return an arbitrary exponent with a confident error.
Processing steps · Applicability · Limitations · Provenance
Processing steps
- Keep rows where the response and every predictor are finite and positive; take logs and prepend an intercept column.
- Fit by ordinary least squares (or Huber IRLS with
robust). - Covariance clustered by
groups(statsmodelscov_type="cluster", CR1 small-sample correction); 95 % intervals from Student’s t with $G - 1$ degrees of freedom. - Leverage and Cook’s distance from the least-squares influence of the same design (also for a robust fit, as a diagnostic).
With robust the cluster covariance is that of a weighted fit with the
converged Huber weights held fixed, which slightly understates the error
when rows are down-weighted; stderr_iid is then the Huber fit’s own.
Applicability
Machine-independent.
Limitations
With few groups the cluster-robust error is itself noisy (it has $G - 1$ degrees of freedom); read it together with the group bootstrap and leave-one-group-out results. Errors in the predictors are ignored, so exponents are attenuated where a predictor is noisy; with $\tau_E = W/P$ and $P$ a predictor, the $P$ exponent is biased towards $-1$ by any error in $P$.
Provenance
- Issue #548 Sec. 6: covariance, intervals, influence and robust regression with grouped samples.
- A. C. Cameron and D. L. Miller, J. Human Resources 50 (2015) 317: cluster-robust inference and the few-clusters problem.
fit_confinement_scaling_odr
Machine-independent
source
fit_confinement_scaling_odr(response, predictors, *, sigma_log_response, sigma_log_predictors)
Errors-in-variables (orthogonal distance) power-law fit in log space.
| Name | Type | Unit | Description |
|---|---|---|---|
response | numpy.ndarray | any | The fitted quantity. For the $\tau_E = W/P$ coupling, fit $W$, not $\tau_E$, so its error is independent of the power’s. |
predictors | Mapping | any | Predictor arrays keyed by name. |
sigma_log_response | float or array-like | - | Standard deviation of the equation error of $\ln y$: measurement error plus the scaling’s intrinsic scatter, not measurement error alone. One number for every row, or one per input row (e.g. a reconstruction’s model-form spread, #579); a row whose value is not finite and positive is dropped. |
sigma_log_predictors | Mapping | - | Standard deviation of the measurement error of $\ln x_j$ per predictor; zero marks a predictor as exact. |
| Returns | |||
| — | dict | any |
|
Raises
ValueErrorNo predictor, mismatched lengths, a non-positive response error, a negative or non-finite or missing predictor error, no noisy predictor, or fewer rows than coefficients plus one.
Processing steps · Applicability · Limitations · Provenance
Processing steps
- Keep rows where everything is finite and positive; take logs.
- Partial the exact predictors (zero error) and the intercept out of the response and the noisy predictors by least squares.
- Scale each residual column by its error and take the right singular vector $v$ of the smallest singular value (total least squares); the noisy slopes are $b_j = -(v_j/\sigma_j)/(v_y/\sigma_y)$.
- The exact predictors’ slopes and the intercept by least squares of $y - \sum_{j\in\mathrm{noisy}} b_j x_j$ on them.
- With per-row response errors steps 2-4 have no closed form: the same linear model is solved as weighted orthogonal distance regression: the maximum-likelihood criterion of a linear model with independent Gaussian errors, $\sum_i (y_i - \beta\cdot x_i)^2 / (\sigma_{y,i}^2 + \sum_j \beta_j^2\sigma_{x,j}^2)$, minimised from the least-squares start (exact predictors carry $\sigma = 0$). For a constant per-row error it reproduces steps 2-4.
Applicability
Machine-independent.
Limitations
The answer depends only on the assumed error ratios, so report it as a function of them. Understating the equation error of the response over-corrects the attenuation and inflates the noisy slopes, the classic pitfall of errors-in-variables confinement fits. Errors are assumed independent between variables. Fitting $\tau_E = W/P$ against $P$ violates that, which is why the response should be $W$ (its $P$ exponent is $1 + \alpha_P$). No standard error is returned: rows of one shot are not independent, so use a shot bootstrap around this function.
Provenance
- Issue #548 Sec. 6: errors-in-variables regression, and the coupling from $\tau_E = W/P$ with $P$ a predictor.
- S. Van Huffel and J. Vandewalle, The Total Least Squares Problem, SIAM (1991), Ch. 3: mixed exact/noisy columns (partial TLS); for a linear model with known error ratios it is the orthogonal distance (maximum-likelihood) solution.
fit_constrained_confinement_scaling
Machine-independent
source
fit_constrained_confinement_scaling(response, predictors, groups, constraint, rhs)
Log-linear power-law fit under one linear equality constraint on the exponents.
| Name | Type | Unit | Description |
|---|---|---|---|
response | numpy.ndarray | any | The fitted quantity, usually $\tau_E$. |
predictors | Mapping | any | Predictor arrays keyed by name. |
groups | numpy.ndarray | - | Group label per row (the shot). |
constraint | Mapping | - | Coefficient per predictor name; omitted predictors (and the intercept) enter with zero. |
rhs | float | - | Right-hand side of |
| Returns | |||
| — | ConfinementScalingFit | any | As |
Raises
ValueErrorAsfit_confinement_scaling, or a constraint naming an unknown predictor or with all-zero coefficients.
Processing steps · Applicability · Limitations · Provenance
Processing steps
- The unconstrained least-squares fit and its clustered covariance $V$
(
fit_confinement_scaling). - Restricted least squares, $\beta_c = \beta - (X^\top X)^{-1}c^\top(c(X^\top X)^{-1}c^\top)^{-1}(c\beta - r)$.
- Covariance $MVM^\top$ with $M = I - (X^\top X)^{-1}c^\top(c(X^\top X)^{-1}c^\top)^{-1}c$.
- Residuals, $R^2$, the restricted leverage
$H - XAc^\top(cAc^\top)^{-1}cAX^\top$ ($A = (X^\top X)^{-1}$) and Cook’s
distance with $k - 1$ free coefficients;
stderr_iidfrom $s^2 MAM^\top$ with the constrained residual variance.
Applicability
Machine-independent.
Limitations
Least squares only (no robust option).
Provenance
- Issue #548 Sec. 8: closures fitted as constraints and compared.
- W. H. Greene, Econometric Analysis, 7th ed., Pearson (2012), Sec. 5.5: restricted least squares.
infer_kadomtsev_size_exponent
Machine-independent
source
infer_kadomtsev_size_exponent(alpha, cov)
Show source vaft/process/confinement.py, lines 1635–1691
def infer_kadomtsev_size_exponent(
alpha: Mapping[str, float],
cov: np.ndarray,
) -> tuple[float, float]:
"""Size exponent the Kadomtsev constraint implies for fitted I, B, P (and n) exponents.
Parameters
----------
alpha : Mapping
Fitted exponents keyed ``i_p``, ``b_t``, ``p_net`` and, optionally,
``n_e`` (absent means zero by model definition, not measured) [-].
cov : numpy.ndarray
Their covariance, rows and columns in the order of ``alpha`` [-].
Returns
-------
alpha_R : float
$\\alpha_R^{\\mathrm{KCT}} = \\tfrac14\\alpha_I + \\tfrac54\\alpha_B
+ \\tfrac34\\alpha_P + 2\\alpha_n + \\tfrac54$ [-].
stderr : float
Its standard error, $\\sqrt{g^\\top C g}$ with $g$ the coefficients
above [-].
Raises
------
ValueError
A missing or unknown exponent name, or a covariance of the wrong
shape.
Applicability
-------------
Machine-independent.
Limitations
-----------
An *assumed* size exponent, not a measured one: one machine's shot-to-shot
radius changes are not a size scan, and a Kadomtsev-completed scaling
satisfies the constraint by construction, so a zero residual is no
evidence for it. Label every use accordingly (#548 Sec. 7).
Provenance
----------
.. [548] Issue #548 Sec. 7: the Connor-Taylor/Kadomtsev completion and its
covariance propagation.
.. [Kadomtsev] B. B. Kadomtsev, *Sov. J. Plasma Phys.* **1** (1975) 295.
"""
weights = {"i_p": 0.25, "b_t": 1.25, "p_net": 0.75, "n_e": 2.0}
names = list(alpha)
unknown = [name for name in names if name not in weights]
if unknown or not {"i_p", "b_t", "p_net"} <= set(names):
raise ValueError(f"exponents must be i_p, b_t, p_net and optionally n_e; got {names}")
c = np.atleast_2d(np.asarray(cov, dtype=float))
if c.shape != (len(names), len(names)):
raise ValueError(f"cov must be {len(names)}x{len(names)}, got {c.shape}")
g = np.array([weights[name] for name in names])
value = float(g @ np.array([float(alpha[name]) for name in names]) + 1.25)
return value, float(np.sqrt(max(g @ c @ g, 0.0)))Size exponent the Kadomtsev constraint implies for fitted I, B, P (and n) exponents.
| Name | Type | Unit | Description |
|---|---|---|---|
alpha | Mapping | - | Fitted exponents keyed |
cov | numpy.ndarray | - | Their covariance, rows and columns in the order of |
| Returns | |||
alpha_R | float | - | $\alpha_R^{\mathrm{KCT}} = \tfrac14\alpha_I + \tfrac54\alpha_B + \tfrac34\alpha_P + 2\alpha_n + \tfrac54$. |
stderr | float | - | Its standard error, $\sqrt{g^\top C g}$ with $g$ the coefficients above. |
Raises
ValueErrorA missing or unknown exponent name, or a covariance of the wrong shape.
Applicability · Limitations · Provenance
Applicability
Machine-independent.
Limitations
An assumed size exponent, not a measured one: one machine’s shot-to-shot radius changes are not a size scan, and a Kadomtsev-completed scaling satisfies the constraint by construction, so a zero residual is no evidence for it. Label every use accordingly (#548 Sec. 7).
Provenance
- Issue #548 Sec. 7: the Connor-Taylor/Kadomtsev completion and its covariance propagation.
- B. B. Kadomtsev, Sov. J. Plasma Phys. 1 (1975) 295.
leave_one_group_out_scaling
Machine-independent
source
leave_one_group_out_scaling(response, predictors, groups)
Show source vaft/process/confinement.py, lines 1421–1483
def leave_one_group_out_scaling(
response: np.ndarray,
predictors: Mapping[str, np.ndarray],
groups: np.ndarray,
) -> dict:
"""Leave-one-group-out refits: coefficient stability and out-of-group prediction error.
Parameters
----------
response : numpy.ndarray
The fitted quantity [any].
predictors : Mapping
Predictor arrays keyed by name [any].
groups : numpy.ndarray
Group label per row; each group is held out in turn [-].
Returns
-------
dict
``names`` (``log_C`` first) [-]; ``groups``, the held-out labels [-];
``coef``, the refit coefficients per held-out group, NaN where the
rest could not identify them, shape ``(G, k)`` [-]; ``log_error``, the
log prediction error of every kept row when its group was held out
[-]; ``rmse_log``, its RMS over rows [-] [any].
Raises
------
ValueError
As :func:`fit_confinement_scaling` for the full sample.
Applicability
-------------
Machine-independent.
Limitations
-----------
A held-out shot whose predictor values lie outside the others' range
tests extrapolation, not interpolation; the prediction error of such a
group dominates ``rmse_log``.
Provenance
----------
.. [548] Issue #548 Sec. 6 and 8: leave-one-shot-out error as a model
comparison criterion.
"""
names, log_y, x, g, _ = _log_design(response, predictors, groups)
labels, codes = np.unique(g, return_inverse=True)
k = x.shape[1]
coef = np.full((labels.size, k), np.nan)
error = np.full(log_y.size, np.nan)
for i in range(labels.size):
held = codes == i
xs, ys = x[~held], log_y[~held]
if xs.shape[0] <= k or np.linalg.matrix_rank(xs) < k:
continue
beta, *_ = np.linalg.lstsq(xs, ys, rcond=None)
coef[i] = beta
error[held] = log_y[held] - x[held] @ beta
finite = np.isfinite(error)
return {
"names": ("log_C", *names), "groups": labels, "coef": coef, "log_error": error,
"rmse_log": float(np.sqrt(np.mean(error[finite] ** 2))) if finite.any() else np.nan,
}Leave-one-group-out refits: coefficient stability and out-of-group prediction error.
| Name | Type | Unit | Description |
|---|---|---|---|
response | numpy.ndarray | any | The fitted quantity. |
predictors | Mapping | any | Predictor arrays keyed by name. |
groups | numpy.ndarray | - | Group label per row; each group is held out in turn. |
| Returns | |||
| — | dict | any |
|
Raises
ValueErrorAsfit_confinement_scalingfor the full sample.
Applicability · Limitations · Provenance
Applicability
Machine-independent.
Limitations
A held-out shot whose predictor values lie outside the others’ range
tests extrapolation, not interpolation; the prediction error of such a
group dominates rmse_log.
Provenance
- Issue #548 Sec. 6 and 8: leave-one-shot-out error as a model comparison criterion.
ohmic_confinement_series
Machine-independent
source
ohmic_confinement_series(time, i_p, dip_dt, v_loop, li_3, r0, w, *, rate_window_s, rate_polyorder=1, p_rad=None, hold_li3_where_missing=True, assumptions=None)
Ohmic power balance and confinement time from a loop voltage, the internal inductance and a stored energy.
| Name | Type | Unit | Description |
|---|---|---|---|
time | numpy.ndarray | s | Times of the samples, strictly increasing; the slices an energy rate may be taken across, so only validated equilibrium slices. |
i_p | numpy.ndarray | A | Plasma current at |
dip_dt | numpy.ndarray | A/s | Its time derivative, from the current record (finer than |
v_loop | numpy.ndarray | V | Loop voltage at |
li_3 | numpy.ndarray | - | Normalised internal inductance $l_{i,3}$ at |
r0 | float | m | Major radius normalising $l_{i,3}$, not the instantaneous $R_\mathrm{geo}$. |
w | numpy.ndarray | J | Stored energy whose rate and confinement are wanted; its definition (EFIT pressure integral, kinetic, global) belongs in |
rate_window_s | float | s | Full width of the local-polynomial window of $dl_{i,3}/dt$ and $dW/dt$. |
rate_polyorder | int, optional | - | Degree of that local polynomial. |
p_rad | numpy.ndarray, optional | W | Measured radiated power; |
hold_li3_where_missing | bool, optional | - | Hold the inductance fixed where the window holds no $l_{i,3}$ rate, so |
assumptions | Mapping[str, str], optional | - | Caller’s physical choices to record (voltage source, energy definition, current source); merged over the defaults. |
| Returns | |||
| — | OhmicConfinement | any | Every input, rate, voltage term, power and confinement time on |
Raises
ValueErrorArrays of different length, a non-increasing time axis, a non-positiver0or window, orrate_polyorderbelow 1.
Processing steps · Defaults · Applicability · Limitations · Provenance
Processing steps
- $dl_{i,3}/dt$ by
smoothed_time_derivativeoverrate_window_s(NaN with fewer than two samples). resistive_loop_voltage: $V_\mathrm{ind} = L_i\,dI_p/dt + \tfrac14\mu_0 R_0 I_p\,dl_{i,3}/dt$, $V_\mathrm{res} = V_\mathrm{loop} - V_\mathrm{ind}$, $R_p = V_\mathrm{res}/I_p$.confinement_power_balance: $P_\mathrm{OH} = I_p V_\mathrm{res}$, $dW/dt$ over the same window, $P_\mathrm{net} = P_\mathrm{OH} - dW/dt$, $P_\mathrm{transport} = P_\mathrm{net} - P_\mathrm{rad}$, $\tau_E = W/P$ where that power is positive.- With a single sample no rate exists: $P_\mathrm{OH}$ is kept (inductance held fixed), $dW/dt$ and everything built on it are NaN. A missing energy rate is never replaced by zero.
Defaults
rate_polyorder = 1 and hold_li3_where_missing = True are the VEST
Tier A reference settings of #548 (3 ms holds about three EFIT slices),
numerical choices recorded in settings, not physical constants.
Applicability
Machine-independent. Ohmic plasmas: no auxiliary heating and no non-inductive current (with one, $R_p$ needs $I_p - I_\mathrm{ni}$).
Limitations
v_res is inferred, and exact only for a loop on the plasma boundary
(recorded in assumptions['v_res']). Holding $l_{i,3}$ fixed is a
numerical fallback, not evidence of a stationary current profile.
Smoothing windows bias rates that change within them.
Provenance
- J. A. Romero et al., Nucl. Fusion 50 (2010) 115002, eq. 24.
- Issue #548 Sec. 1 and
workflow/confinement_scaling: the VEST Tier A reference chain this function was moved out of. - Issue #1905 Sec. 3: the shared confinement contract and its required result fields.
predictor_principal_directions
Machine-independent
source
predictor_principal_directions(predictors, *, groups=None)
Principal directions of the log predictors: which exponent combinations the data constrain.
| Name | Type | Unit | Description |
|---|---|---|---|
predictors | Mapping | any | Predictor arrays keyed by name; rows with any non-finite or non-positive value are dropped. |
groups | array-like, optional | any | Group label per row (e.g. shot); when given, the analysis is repeated on the shot means (between-group) and on the within-group deviations |
| Returns | |||
| — | dict | any |
|
Raises
ValueErrorNo predictor, mismatched lengths (groupsincluded), fewer than three usable rows, or a predictor that does not vary.
Processing steps · Applicability · Limitations · Provenance
Processing steps
- Take logs, centre and divide each column by its standard deviation.
- Singular-value decompose; a direction with a small singular value is a combination of predictors the sample barely varies, so the matching combination of exponents is poorly determined (PCR diagnostic). With fewer rows than predictors the missing directions are returned with singular value zero: they are the unconstrained ones.
- With
groups, split each direction’s spread into the part carried by the group means (each row taking its group’s mean) and the part carried by the within-group deviations, both with the pooled centring and scale.
Applicability
Machine-independent.
Limitations
Directions are in standardised log coordinates; a direction mixes
exponents in units of each predictor’s spread, so convert with scale
before reading it as a physical scan. It diagnoses the design, not a fit:
it says which scan would add information, not which exponent is right.
Provenance
- Issue #1621 Secs. 8 and 12: PCR-style identifiability and the weak singular directions that point to the next scan.
- D. A. Belsley, E. Kuh and R. E. Welsch, Regression Diagnostics, Wiley (1980), Ch. 3.
resistive_loop_voltage
Machine-independent
source
resistive_loop_voltage(v_loop, i_p, dip_dt, li_3, r0, *, dli3_dt=None, hold_li3_where_missing=False)
Inductive and resistive parts of a loop voltage, and the plasma resistance, from the internal inductance.
| Name | Type | Unit | Description |
|---|---|---|---|
v_loop | numpy.ndarray | V | Loop voltage, in the orientation of |
i_p | numpy.ndarray | A | Plasma current. |
dip_dt | numpy.ndarray | A/s | Its time derivative. |
li_3 | numpy.ndarray | - | Normalised internal inductance $l_{i,3}$. |
r0 | float | m | Major radius normalising $l_{i,3}$. |
dli3_dt | numpy.ndarray, optional | 1/s | Time derivative of $l_{i,3}$; |
hold_li3_where_missing | bool, optional | - | Where |
| Returns | |||
| — | ResistiveLoopVoltage | any |
|
Raises
ValueErrorArrays of different length or a non-positiver0.
Processing steps · Applicability · Limitations · Provenance
Processing steps
- $L_i = \mu_0 R_0 l_{i,3}/2$, so $W_\mathrm{int} = L_i I_p^2/2$.
- $V_\mathrm{ind} = (1/I_p)\,dW_\mathrm{int}/dt
= L_i\,dI_p/dt + \tfrac14\mu_0 R_0 I_p\,dl_{i,3}/dt$, the second
term only where
dli3_dtis given and finite (or, withhold_li3_where_missing, zero where it is not). - $V_\mathrm{res} = V_\mathrm{loop} - V_\mathrm{ind}$ and $R_p = V_\mathrm{res}/I_p$.
Applicability
Machine-independent.
Limitations
Exact (Romero’s eq. 24) only for the loop voltage at the plasma boundary. A flux loop away from the boundary also sees the external inductive flux between loop and boundary, which this does not remove.
Provenance
- J. A. Romero et al., Nucl. Fusion 50 (2010) 115002, eq. 24: the inductive voltage of the internal poloidal-field energy.
- Issue #548 Sec. 1: P_OH from the loop voltage with the inductive part removed, the Spitzer path only for comparison (#1188).
resolve_observed_confinement
Machine-independent
source
resolve_observed_confinement(tau_thermal, energy_basis, *, tau_global=None, thermal_as_global=False)
Observed confinement time with the energy basis a scaling was fitted on (issue #1713).
| Name | Type | Unit | Description |
|---|---|---|---|
tau_thermal | array-like | s | Thermal confinement time $W_{th}/P$ per row, NaN where unknown. |
energy_basis | str | str | The scaling’s basis, from |
tau_global | array-like, optional | s | Global confinement time $W/P$ per row, fast ions included; default none observed. |
thermal_as_global | bool, optional | - | Use the thermal time where a global (or unaudited) basis has no global observation, recording |
| Returns | |||
| — | ObservedConfinement | any | The resolved times and their provenance. |
Raises
ValueErrorAn unknownenergy_basis, or arrays of different lengths.
Processing steps · Applicability · Limitations · Provenance
Processing steps
- A thermal scaling takes
tau_thermal; nothing else stands in for it. - A global scaling takes
tau_globalwhere it is finite. Elsewhere it is NaN (strict), or the thermal time withTHERMAL_AS_GLOBALrecorded whenthermal_as_globalis set. - An unaudited scaling has no known basis: NaN unless
thermal_as_global, when the thermal time is used and recorded.
Applicability
Machine-independent. The thermal-for-global substitution is defensible for ohmic plasmas without fast ions (VEST Tier A) and wrong for beam- or RF-heated plasmas with a fast-ion population.
Limitations
Resolves which time to use; it does not construct $W_{global}$ or
$W_{th}$ from a reconstruction. w_mhd_J from a magnetics EFIT is total
kinetic pressure and is not by itself a thermal energy.
Provenance
- Issue #1713: thermal and global energy confinement must not be silently interchanged in scaling comparisons.
smoothed_time_derivative
Machine-independent
source
smoothed_time_derivative(time, data, *, window_s=None, polyorder=2, at=None)
Time derivative of a sampled quantity by a local polynomial fit, on any time grid.
| Name | Type | Unit | Description |
|---|---|---|---|
time | numpy.ndarray | s | Sample times, strictly increasing, 1-D; gaps are allowed. |
data | numpy.ndarray | any | The sampled quantity, same length as |
window_s | float, optional | s | Full width of the fitting window centred on each evaluation time; |
polyorder | int, optional | - | Degree of the local polynomial. |
at | numpy.ndarray, optional | s | Times to evaluate the derivative at; default the samples themselves. Needs |
| Returns | |||
| — | numpy.ndarray | any/s |
|
Raises
ValueErrorArrays of different length, fewer than two samples, a non-increasing time axis, a non-positive window,polyorderbelow 1 with a window, oratwithoutwindow_s.
Processing steps · Defaults · Applicability · Limitations · Provenance
Processing steps
- Without
window_s: the weighted central difference at the samples (one-sided at the ends). - With
window_s: at each evaluation time $t_0$, the finite samples with $|t - t_0| \le$window_s/2 are fitted by a degree-polyorderpolynomial in $t - t_0$ (least squares, unweighted); the derivative is its linear coefficient. On a uniform grid away from the ends this is the Savitzky-Golay first derivative; at the ends and across gaps the window is simply one-sided or sparser.
Defaults
polyorder = 2: numerical convenience, the lowest degree whose
derivative is unbiased for a quantity with constant curvature.
Applicability
Machine-independent.
Limitations
Smoothing biases a derivative that changes within the window; the window width belongs in the provenance of every quantity built on it. Near the ends the window is one-sided and the derivative noisier.
Provenance
- A. Savitzky and M. J. E. Golay, Anal. Chem. 36 (1964) 1627: the local least-squares polynomial derivative.