66 static_assert(Degree >= 1 && Degree <= 11 && Degree % 2 == 1);
67 static_assert(MomentCount >= 1 && MomentCount <= 8);
68 static_assert(Degree + MomentCount <= 16);
69 using Moments = std::array<double, MomentCount>;
70 static constexpr bool rational =
71 C == Curve::GoldenRatio || C == Curve::SymmetricKnee || C == Curve::AsymmetricKnee;
72 static constexpr int count = Degree + 1;
73 static constexpr double referenceSlope = NativeSlope ? clipperSmallSignalSlope<C, double>() : 1.;
74 using Poly = std::array<double, count>;
75 std::array<Poly, count> matrix_{}, bernsteinMatrix_{};
79 double reconstructionBound_ = 1;
80 double arithmeticBound_ = 1;
81 std::array<double, 11> levels_{};
83 static double value(
const Poly &p,
double t)
86 for (
int i = Degree - 1; i >= 0; --i)
90 static double slope(
const Poly &p,
double t)
92 double v = Degree * p[Degree];
93 for (
int i = Degree - 1; i >= 1; --i)
97 static void add(Moments &a,
const Moments &b)
99 for (
int i = 0; i < MomentCount; ++i)
103 Moments quadrature(
const Poly &p,
double a,
double b,
const Gauss<N> &q,
bool fixed =
false,
104 double fixedValue = 0)
const
107 for (
int i = 0; i < N; ++i)
109 const double t = a + (b - a) * q.
nodes[i], x = value(p, t);
110 const double reference = SubtractLinear ? referenceSlope * x : 0.;
111 double v = (b - a) * q.
weights[i] *
112 (fixed ? fixedValue - reference : clipperShape<C>(x, ceiling_) - reference);
113 for (
int j = 0; j < MomentCount; ++j)
121 std::array<Moments, 2> tanhQuadrature(
const Poly &p,
double a,
double b)
const
126 static constexpr std::array<double, 8> nodes{
127 .99145537112081263921, .94910791234275852453, .86486442335976907279,
128 .74153118559939443986, .58608723546769113029, .40584515137739716691,
129 .20778495500789846760, 0.};
130 static constexpr std::array<double, 8> fineWeights{
131 .02293532201052922496, .06309209262997855329, .10479001032225018384,
132 .14065325971552591875, .16900472663926790283, .19035057806478540991,
133 .20443294007529889241, .20948214108472782801};
134 static constexpr std::array<double, 4> coarseWeights{
135 .12948496616886969327, .27970539148927666790,
136 .38183005050511894495, .41795918367346938776};
137 std::array<Moments, 2> result{};
138 const double half = (b - a) * .5, center = a + half;
139 const auto accumulate = [&](
double t,
double fineWeight,
double coarseWeight)
141 const double x = value(p, t);
142 const double reference = SubtractLinear ? referenceSlope * x : 0.;
143 double v = half * (clipperShape<C>(x, ceiling_) - reference);
144 for (
int j = 0; j < MomentCount; ++j)
146 result[1][j] += fineWeight * v;
147 if (coarseWeight != 0.) result[0][j] += coarseWeight * v;
151 accumulate(center, fineWeights[7], coarseWeights[3]);
152 for (
int i = 0; i < 7; ++i)
154 const double coarseWeight = i % 2 ? coarseWeights[i / 2] : 0.;
155 accumulate(center - half * nodes[i], fineWeights[i], coarseWeight);
156 accumulate(center + half * nodes[i], fineWeights[i], coarseWeight);
160 Moments adaptive(
const Poly &p,
double a,
double b,
int depth = 0)
const
162 const auto estimates = [&] {
163 if constexpr (C == Curve::Tanh)
return tanhQuadrature(p, a, b);
164 else return std::array<Moments, 2>{quadrature(p, a, b, linearQuadrature_),
165 quadrature(p, a, b, nonlinearQuadrature_)};
167 const auto &coarse = estimates[0], &fine = estimates[1];
169 1 + std::abs(value(p, a)) + std::abs(value(p, b)) + std::abs(value(p, (a + b) / 2));
170 const double tolerance = (b - a) * 1e-13 * scale;
172 for (
int i = 0; i < MomentCount; ++i)
173 error = std::max(error, std::abs(fine[i] - coarse[i]));
174 if (error <= tolerance)
177 return {std::numeric_limits<double>::quiet_NaN()};
178 auto out = adaptive(p, a, (a + b) / 2, depth + 1);
179 add(out, adaptive(p, (a + b) / 2, b, depth + 1));
182 Moments linearMoments(
const Poly &p,
double a,
double b)
const
186 if constexpr (MomentCount != 4)
188 if (a == 0 && b == 1)
190 static constexpr auto weights = [] {
191 std::array<Poly, MomentCount> result{};
192 for (
int j = 0; j < MomentCount; ++j)
193 for (
int k = 0; k < count; ++k)
194 result[j][k] = 1. / (j + k + 1);
198 for (
int j = 0; j < MomentCount; ++j)
199 for (
int k = 0; k < count; ++k)
200 out[j] += p[k] * weights[j][k];
204 return quadrature(p, a, b, linearQuadrature_);
206 Moments segment(
const Poly &p,
double a,
double b)
const
208 const double x = value(p, (a + b) / 2);
209 if constexpr (C == Curve::Hard)
211 if (std::abs(x) <= ceiling_)
213 if constexpr (SubtractLinear)
return {};
214 else return linearMoments(p, a, b);
216 return quadrature(p, a, b, linearQuadrature_,
true, std::copysign(ceiling_, x));
218 else if constexpr (C == Curve::Sine)
220 if (std::abs(x) >= ceiling_ * halfPi<double>)
221 return quadrature(p, a, b, linearQuadrature_,
true, clipperShape<C>(x, ceiling_));
223 else if constexpr (rational)
225 const auto limits = clipperLinearLimits<C>(ceiling_);
226 if (x >= limits[0] && x <= limits[1])
228 if constexpr (SubtractLinear)
return {};
229 else return linearMoments(p, a, b);
232 else if constexpr (C == Curve::Tanh)
234 if (std::abs(x) >= 24 * ceiling_)
235 return quadrature(p, a, b, linearQuadrature_,
true, std::copysign(ceiling_, x));
237 if constexpr (C == Curve::Tanh || rational)
238 return adaptive(p, a, b);
240 return quadrature(p, a, b, nonlinearQuadrature_);
242 double root(
const Poly &p,
double a,
double b,
double target)
const
244 double fa = value(p, a) - target, fb = value(p, b) - target;
249 if ((fa > 0) == (fb > 0))
250 return std::numeric_limits<double>::quiet_NaN();
251 double x = a + (b - a) * (-fa) / (fb - fa);
252 for (
int i = 0; i < 64; ++i)
254 const double f = value(p, x) - target;
255 if (std::abs(f) <= 2e-15 || b - a < 2e-14)
257 if ((f > 0) == (fa > 0))
267 const double d = slope(p, x);
268 const double candidate = d != 0 ? x - f / d : (a + b) / 2;
269 x = candidate > a && candidate < b ? candidate : (a + b) / 2;
271 return std::numeric_limits<double>::quiet_NaN();
273 Moments recurse(
const Poly &p,
const Poly &bernstein,
double a,
double b,
int depth)
const
275 const auto bounds = std::minmax_element(bernstein.begin(), bernstein.end());
276 const double low = *bounds.first, high = *bounds.second;
277 if constexpr (C == Curve::Hard)
279 if (low >= -ceiling_ && high <= ceiling_)
281 if constexpr (SubtractLinear)
return {};
282 else return linearMoments(p, a, b);
285 if constexpr (rational)
287 const auto limits = clipperLinearLimits<C>(ceiling_);
288 if (low >= limits[0] && high <= limits[1])
290 if constexpr (SubtractLinear)
return {};
291 else return linearMoments(p, a, b);
294 bool crosses =
false;
295 for (
int i = 0; i < levelCount_; ++i)
296 crosses = crosses || (low < levels_[i] && levels_[i] < high);
298 return segment(p, a, b);
301 if (high - low < 1e-13)
302 return quadrature(p, a, b, nonlinearQuadrature_);
303 bool increasing =
true, decreasing =
true;
304 for (
int i = 1; i < count; ++i)
306 increasing = increasing && bernstein[i] >= bernstein[i - 1];
307 decreasing = decreasing && bernstein[i] <= bernstein[i - 1];
309 if (increasing || decreasing)
311 std::array<double, 13> edges{};
314 const double va = value(p, a), vb = value(p, b);
315 for (
int i = 0; i < levelCount_; ++i)
316 if (levels_[i] > std::min(va, vb) && levels_[i] < std::max(va, vb))
318 const double crossing = root(p, a, b, levels_[i]);
319 if (!std::isfinite(crossing))
320 return {std::numeric_limits<double>::quiet_NaN()};
321 edges[n++] = crossing;
324 std::sort(edges.begin(), edges.begin() + n);
326 for (
int i = 0; i + 1 < n; ++i)
327 add(out, segment(p, edges[i], edges[i + 1]));
331 return {std::numeric_limits<double>::quiet_NaN()};
332 Poly work = bernstein, left{}, right{};
334 right[Degree] = work[Degree];
335 for (
int level = 1; level < count; ++level)
337 for (
int i = 0; i < count - level; ++i)
338 work[i] = (work[i] + work[i + 1]) * .5;
339 left[level] = work[0];
340 right[Degree - level] = work[Degree - level];
342 auto out = recurse(p, left, a, (a + b) / 2, depth + 1);
343 add(out, recurse(p, right, (a + b) / 2, b, depth + 1));
350 static_assert(MomentCount == 4);
351 constexpr std::array<Moments, 4> kernels{
352 {{1, -3, 3, -1}, {4, 0, -6, 3}, {1, 3, 3, -3}, {0, 0, 0, 1}}};
353 std::vector<double> result(Degree + 4);
354 for (
int i = 0; i < count; ++i)
357 for (
int j = 0; j < 4; ++j)
358 for (
int k = 0; k < count; ++k)
359 moments[j] += matrix_[i][k] / (k + j + 1);
360 for (
int lag = 0; lag < 4; ++lag)
361 for (
int j = 0; j < 4; ++j)
362 result[Degree - i + lag] += moments[j] * kernels[lag][j] / 6;
366 explicit Interval(
double ceiling) : ceiling_(ceiling)
368 constexpr int begin = -(Degree - 1) / 2;
369 for (
int node = 0; node < count; ++node)
371 std::array<long double, count> p{};
374 const int x = begin + node;
375 for (
int other = 0; other < count; ++other)
378 const int y = begin + other;
379 std::array<long double, count> next{};
380 for (
int j = 0; j <= order; ++j)
382 next[j] -= p[j] * y / (x - y);
383 next[j + 1] += p[j] / (x - y);
388 for (
int j = 0; j < count; ++j)
389 matrix_[node][j] =
static_cast<double>(p[j]);
391 for (
int i = 0; i < count; ++i)
392 for (
int j = 0; j <= i; ++j)
393 bernsteinMatrix_[i][j] =
choose(i, j) /
choose(Degree, j);
396 for (
int row = 0; row < count; ++row)
398 long double norm = 0, arithmeticNorm = 0;
399 for (
int node = 0; node < count; ++node)
401 long double coefficient = 0;
402 for (
int column = 0; column <= row; ++column)
404 coefficient +=
static_cast<long double>(bernsteinMatrix_[row][column]) *
405 matrix_[node][column];
407 std::abs(
static_cast<long double>(bernsteinMatrix_[row][column]) *
408 matrix_[node][column]);
410 norm += std::abs(coefficient);
412 reconstructionBound_ = std::max(reconstructionBound_,
static_cast<double>(norm));
413 arithmeticBound_ = std::max(arithmeticBound_,
static_cast<double>(arithmeticNorm));
420 if constexpr (C == Curve::Tanh)
422 levels_ = {-24 * ceiling, -12 * ceiling, -6 * ceiling, -3 * ceiling, -ceiling, 0,
423 ceiling, 3 * ceiling, 6 * ceiling, 12 * ceiling, 24 * ceiling};
426 else if constexpr (rational)
428 const auto limits = clipperLinearLimits<C>(ceiling);
429 levels_[0] = limits[0];
430 levels_[1] = limits[1];
435 const double limit = C == Curve::Sine ? ceiling * halfPi<double> : ceiling;
445 return reconstructionBound_;
453 [[nodiscard]] Moments
polynomial(
const Poly &power)
const noexcept
456 for (
double coefficient : power)
457 if (!std::isfinite(coefficient))
458 return {std::numeric_limits<double>::quiet_NaN()};
459 if (std::all_of(power.begin(), power.end(), [](
double value) { return value == 0; }))
461 for (
int i = 0; i < count; ++i)
462 for (
int j = 0; j <= i; ++j)
463 bernstein[i] += power[j] * bernsteinMatrix_[i][j];
464 return recurse(power, bernstein, 0, 1, 0);
466 Moments
operator()(
const std::array<double, count> &samples)
const noexcept
468 if (std::all_of(samples.begin(), samples.end(), [](
double value) { return value == 0; }))
470 if constexpr (SubtractLinear && (C == Curve::Hard || rational))
472 double low = samples[0], high = samples[0];
473 for (
double sample : samples)
475 if (!std::isfinite(sample))
476 return {std::numeric_limits<double>::quiet_NaN()};
477 low = std::min(low, sample);
478 high = std::max(high, sample);
480 const double center = 0.5 * low + 0.5 * high;
481 const double radius = (0.5 * high - 0.5 * low) * reconstructionBound_;
484 const double margin = 128 * std::numeric_limits<double>::epsilon() * arithmeticBound_ *
485 (std::abs(center) + radius) +
486 256 * std::numeric_limits<double>::denorm_min();
487 const auto limits = [&] {
488 if constexpr (rational)
489 return clipperLinearLimits<C>(ceiling_);
491 return std::array<double, 2>{-ceiling_, ceiling_};
493 if (center - radius - margin >= limits[0] && center + radius + margin <= limits[1])
496 Poly power{}, bernstein{};
497 for (
int i = 0; i < count; ++i)
498 for (
int j = 0; j < count; ++j)
499 power[j] += samples[i] * matrix_[i][j];
500 for (
int i = 0; i < count; ++i)
501 for (
int j = 0; j <= i; ++j)
502 bernstein[i] += power[j] * bernsteinMatrix_[i][j];
503 return recurse(power, bernstein, 0, 1, 0);