68 static double value(
double x)
noexcept {
70 const double x2 = x * x;
71 return x * (1.0 / 3.0 + x2 * (-1.0 / 45.0 + x2 * (2.0 / 945.0 - x2 / 4725.0)));
73 return coth(x) - 1.0 / x;
78 const double x2 = x * x;
79 return 1.0 / 3.0 + x2 * (-1.0 / 15.0 + x2 * (2.0 / 189.0 - x2 / 675.0));
81 const double c =
coth(x);
82 return 1.0 / (x * x) -
c *
c + 1.0;
87 const double x2 = x * x;
88 return x * (-2.0 / 15.0 + x2 * (8.0 / 189.0 - x2 * (2.0 / 225.0)));
90 const double c =
coth(x);
91 return -2.0 / (x * x * x) + 2.0 *
c * (
c *
c - 1.0);
97 static double coth(
double x)
noexcept {
99 const double magnitude = std::max(std::abs(t), 1
e-12);
100 return (t < 0.0 ? -1.0 : 1.0) / magnitude;
107 void prepare(
double solver_rate_hz)
noexcept {
108 period_ = 1.0 / std::max(solver_rate_hz, 1.0);
113 magnetization_ = 0.0;
115 field_derivative_ = 0.0;
116 rate_previous_ = 0.0;
129 const double reference_drive =
kTapeDrive.front();
130 const double safe_drive = std::max(drive, 1
e-3);
132 (reference_drive / safe_drive);
133 shape_ = std::max(shape_, 1
e-4);
136 const double bias_span = std::max(
kTapeBias.back(), 1
e-9);
137 const double t = std::clamp(bias / bias_span, 0.0, 1.0);
138 reversibility_ = 0.3 + t * (0.1 - 0.3);
147 (2.0 / period_) * (field - field_) - rate_previous_;
153 magnetization_ = 0.0;
155 field_derivative_ = 0.0;
156 rate_previous_ = 0.0;
160 const double derivative_previous = field_derivative_;
165 double m = magnetization_ + period_ * derivative_previous;
173 const bool irreversible_active = branch_active(m, field, rate);
177 const double derivative_now = evaluate(m, field, rate, slope, irreversible_active);
178 const double residual =
179 m - magnetization_ - 0.5 * period_ * (derivative_now + derivative_previous);
189 const double jacobian = 1.0 - 0.5 * period_ * slope;
190 if (std::abs(jacobian) < 1
e-12)
break;
191 const double delta = residual / jacobian;
200 double unused_slope = 0.0;
201 field_derivative_ = evaluate(m, field, rate, unused_slope, irreversible_active);
203 if (!std::isfinite(m) || !std::isfinite(field_derivative_)) {
208 field_derivative_ = 0.0;
209 rate_previous_ = 0.0;
211 magnetization_ = 0.0;
217 rate_previous_ = rate;
218 return magnetization_;
236 bool branch_active(
double m,
double h,
double rate)
const noexcept {
241 return (difference > 0.0) == (rate >= 0.0) && difference != 0.0;
244 double evaluate(
double m,
double h,
double rate,
double& slope,
245 bool irreversible_active)
const noexcept {
254 const double difference = anhysteretic - m;
255 const double delta = (rate >= 0.0) ? 1.0 : -1.0;
256 const double delta_m = irreversible_active ? 1.0 : 0.0;
258 const double one_minus_c = 1.0 - reversibility_;
260 if (std::abs(denominator) < 1
e-12) denominator = (denominator < 0.0 ? -1
e-12 : 1
e-12);
262 const double irreversible = one_minus_c * delta_m * difference / denominator;
263 const double reversible = reversibility_ * langevin_first / shape_;
264 const double dm_dh = irreversible + reversible;
268 const double u = langevin_first * dq_dm - 1.0;
269 const double irreversible_slope = one_minus_c * delta_m *
u * (one_minus_c * delta * pinning_) /
270 (denominator * denominator);
271 const double reversible_slope = reversibility_ * langevin_second * dq_dm / shape_;
273 slope = (irreversible_slope + reversible_slope) * rate;
277 double period_ = 1.0 / 192000.0;
278 double magnetization_ = 0.0;
280 double field_derivative_ = 0.0;
281 double rate_previous_ = 0.0;
287 std::size_t converged_ = 0;
288 std::size_t capped_ = 0;
298template <std::
size_t StageCount>
301 static_assert(StageCount > 0);
304 return 1 << StageCount;
315 for (std::size_t stage = 0; stage < StageCount; ++stage) {
317 static_cast<double>(1u << stage);
325 for (
auto& stage : stages_) stage.reset();
332 for (std::size_t stage = 0; stage < StageCount; ++stage)
333 latency += (taps - 1) /
static_cast<int>(1u << (stage + 1u));
337 template <
typename Callback>
338 double process(
double x, Callback&& callback) {
339 return process_stage<0>(x, callback);
343 template <std::
size_t Stage,
typename Callback>
344 double process_stage(
double x, Callback& callback) {
345 if constexpr (Stage == StageCount) {
350 stages_[Stage].upsample(x, even, odd);
354 const double processed_even = process_stage<Stage + 1u>(even, callback);
355 const double processed_odd = process_stage<Stage + 1u>(odd, callback);
356 return stages_[Stage].downsample(processed_even, processed_odd);
360 std::array<detail::LinearPhaseOversamplingStage2x<double>, StageCount> stages_{};
385template <
typename Wrapper>
389 return Wrapper::oversampling_factor();
393 return Wrapper::latency_samples();
407 solver_.set_character(drive, bias);
411 return wrapper_.process(
412 input, [
this](
double sample) {
return solver_.process(sample); });
Fixed-depth linear-phase half-band oversampling around a per-sample callback.
Definition hysteresis.hpp:299
static constexpr int oversampling_factor() noexcept
Definition hysteresis.hpp:303
void reset() noexcept
Definition hysteresis.hpp:324
static int latency_samples() noexcept
Group delay of the complete up/down cascade, in host samples.
Definition hysteresis.hpp:329
void prepare()
Definition hysteresis.hpp:307
double process(double x, Callback &&callback)
Definition hysteresis.hpp:338
The hysteresis stage for one channel, in normalized units.
Definition hysteresis.hpp:105
void prepare(double solver_rate_hz) noexcept
Definition hysteresis.hpp:107
void clear_solver_counters() noexcept
Definition hysteresis.hpp:225
void set_character(double drive, double bias) noexcept
drive and bias come from the tape character table; both are re-mapped onto physical constants here ra...
Definition hysteresis.hpp:128
void reset() noexcept
Definition hysteresis.hpp:112
std::size_t converged_steps() const noexcept
Diagnostics for the acceptance suite: how often the solver converged inside the iteration cap versus ...
Definition hysteresis.hpp:223
std::size_t capped_steps() const noexcept
Definition hysteresis.hpp:224
double process(double field) noexcept
One solver step.
Definition hysteresis.hpp:143
Production-owned composition of a fixed wrapper and magnetic solver.
Definition hysteresis.hpp:386
double process(double input)
Definition hysteresis.hpp:410
void prepare(double host_rate_hz)
Definition hysteresis.hpp:396
void set_character(double drive, double bias) noexcept
Definition hysteresis.hpp:406
static int latency_samples() noexcept
Definition hysteresis.hpp:392
static constexpr int oversampling_factor() noexcept
Definition hysteresis.hpp:388
void reset() noexcept
Definition hysteresis.hpp:401
HysteresisHalfBandOversampler< 2 > HalfBandOversampler4x
The two depths the module has a measured reason to name.
Definition hysteresis.hpp:378
OversampledHysteresis< HysteresisOversampler8x > OversampledHysteresis8x
Definition hysteresis.hpp:421
constexpr int kJaMaxIterations
Newton-Raphson budget.
Definition tables.hpp:373
OversampledHysteresis< HalfBandOversampler4x > OversampledHysteresis4x
Definition hysteresis.hpp:420
constexpr std::array< double, 4 > kTapeBias
Definition tables.hpp:141
double flush_denormal(double v) noexcept
Definition primitives.hpp:45
constexpr double kJaPinning
Definition tables.hpp:366
constexpr double kJaSilenceThreshold
Below this |H| and |dH/dt|, magnetization is snapped to 0 so a silent loop cannot hold a frozen DC ma...
Definition tables.hpp:381
double fast_tanh(double v) noexcept
Definition primitives.hpp:57
constexpr double kJaInterdomainCoupling
Definition tables.hpp:368
constexpr double kHysteresisHalfBandStopbandDb
Definition tables.hpp:388
constexpr std::array< double, 4 > kTapeDrive
Definition tables.hpp:140
constexpr double kJaLangevinClamp
Langevin argument clamp — keeps coth() and its derivative in the range where the rational approximati...
Definition tables.hpp:378
constexpr double kJaReversibility
Definition tables.hpp:367
constexpr double kJaAnhystereticShape
Definition tables.hpp:365
constexpr double kJaConvergenceThreshold
Definition tables.hpp:374
constexpr double kJaSaturationMagnetization
PUBLISHED γ-Fe2O3 material constants (Jiles, Thoelke & Devine 1992, reproduced in Chowdhury 2019).
Definition tables.hpp:364
constexpr std::size_t kHysteresisHalfBandTaps
Hysteresis oversampling factor and half-band design.
Definition tables.hpp:387
HysteresisHalfBandOversampler< 3 > HysteresisOversampler8x
Definition hysteresis.hpp:379
@ c
Definition spectrum_trace.hpp:35
constexpr double kHouseHalfBandStageTwoPassband
Definition oversampling_fir.hpp:24
@ u
Definition formant_filter_bank.hpp:20
@ e
Definition formant_filter_bank.hpp:20
constexpr double kHouseHalfBandStageOnePassband
Definition oversampling_fir.hpp:23
Linear-phase 2x FIR stage used by the realtime oversampler.
Langevin function L(x) = coth(x) − 1/x and its first two derivatives.
Definition hysteresis.hpp:65
static double value(double x) noexcept
Definition hysteresis.hpp:68
static double coth(double x) noexcept
coth via the sanctioned rational tanh approximation and a reciprocal.
Definition hysteresis.hpp:97
static constexpr double kSeriesLimit
Definition hysteresis.hpp:66
static double derivative(double x) noexcept
Definition hysteresis.hpp:76
static double second_derivative(double x) noexcept
Definition hysteresis.hpp:85