rizer.cantera_ext
C++ Cantera 1-D plasma extension (custom Domain1D models and solvers)
Loading...
Searching...
No Matches
PropertyTable.h
Go to the documentation of this file.
1// PropertyTable: an immutable 1D piecewise-linear lookup with endpoint
2// clamping.
3//
4// The abscissa is whatever the caller tabulated against, not necessarily a
5// temperature. The same container holds, in this extension:
6// - sigma(T), kappa(T), P_rad(T) and the optional seed profile T_seed(r)
7// of ThermalPlasmaColumn1D (steady LTE column);
8// - the sigma(Te) and nu_m(Te) fallbacks of PlasmaCell, and CollisionModel's
9// Maxwellian kernel Qbar(Te) together with the momentum-transfer cross
10// section sigma(energy) it is built from;
11// - kappa(T), kappa_e(T) and the initial radial profiles Tg(r), Te(r),
12// Y_s(r), rho(r), u_r(r) of PlasmaChannel1D;
13// - the applied field E(t) of the transient solver.
14// eval()'s argument and return carry the units of whatever was tabulated;
15// the class itself is unit-agnostic.
16//
17// Three shapes, decided in one place. fromTableOrScalar() maps a caller's
18// (grid, values) pair onto exactly one of
19// - a real table, from >= 2 matching points;
20// - a constant, from one value against an empty or one-point grid;
21// - an empty table, when neither is, whose eval() returns 0.0 -- which is
22// how "this property is not configured" is spelled.
23// Any other pair throws, naming the pair. It has to: an empty table is
24// indistinguishable from a property that is genuinely zero everywhere, so
25// returning one for an off-by-one in the caller's arrays would turn a setup
26// error into a silently different physical problem rather than a failure.
27//
28// Ordering is checked at construction, and is non-decreasing rather than
29// strictly increasing. A repeated knot is legal and is how a step profile is
30// written (two values at one abscissa), which NUMERICS.md section "Validation ladder"'s P3
31// initial condition needs. A decreasing knot throws, because eval() would not
32// fail on one: std::upper_bound returns an arbitrary bracket there, and the
33// interpolation weight leaves [0, 1] for the rest of the run.
34//
35// Outside [front, back] eval() clamps to the endpoint value: no
36// extrapolation, no NaN, and no error either. The transient closures evaluate
37// their tables through numeric/TableLookup.h, which refuses a state outside
38// them, and PlasmaChannel1D checks its initial profiles at construction. Three
39// lookups still clamp: ThermalPlasmaColumn1D's Newton residual (its converged
40// column is checked after the solve instead), CollisionModel::meanCrossSection's
41// sigma(energy), whose integral runs past any finite table by design, and the
42// LTE column's seed profile.
43//
44// Nothing here reads a file. Every table is built by the caller, either from
45// numpy arrays through bindings.cpp's makeTable() or from a Config pair
46// through fromTableOrScalar().
47
48#ifndef RIZER_PROPERTY_TABLE_H
49#define RIZER_PROPERTY_TABLE_H
50
51#include <vector>
52#include <stdexcept>
53#include <algorithm>
54#include <string>
55
56namespace rizer
57{
58
60 {
61 public:
62 PropertyTable() = default;
63
70 PropertyTable(std::vector<double> T, std::vector<double> values)
71 : m_T(std::move(T)), m_v(std::move(values))
72 {
73 if (m_T.size() != m_v.size() || m_T.size() < 2)
74 {
75 throw std::invalid_argument(
76 "PropertyTable: T and values must have equal length >= 2");
77 }
78 // eval() brackets with std::upper_bound and clamps on front()/back(),
79 // both of which need the knots ordered. An unsorted grid does not
80 // fail there -- it returns a value off an arbitrary bracket, with the
81 // interpolation weight outside [0, 1], for the rest of the run.
82 //
83 // Non-decreasing, not strictly increasing: a REPEATED knot is how a
84 // step profile is written (two values at the same radius), which the
85 // step-function initial condition of NUMERICS.md section "Validation ladder" needs.
86 // Repeats cannot divide by zero below, because upper_bound returns the
87 // first knot strictly greater than T, so m_T[hi] > T >= m_T[hi-1]
88 // forces m_T[hi] != m_T[hi-1].
89 for (std::size_t i = 1; i < m_T.size(); i++)
90 {
91 if (m_T[i] < m_T[i - 1])
92 {
93 throw std::invalid_argument(
94 "PropertyTable: T must be non-decreasing, but T[" +
95 std::to_string(i) + "] = " + std::to_string(m_T[i]) +
96 " is below T[" + std::to_string(i - 1) +
97 "] = " + std::to_string(m_T[i - 1]) + ".");
98 }
99 }
100 }
101
105 static PropertyTable constant(double value)
106 {
107 return PropertyTable({0.0, 1.0e9}, {value, value});
108 }
109
126 static PropertyTable fromTableOrScalar(const std::vector<double> &x,
127 const std::vector<double> &v,
128 const char *name = "property table")
129 {
130 if (x.size() >= 2 && x.size() == v.size())
131 return PropertyTable(x, v);
132 // A grid of two or more points against one value is the mismatched
133 // pair below, not a constant: dropping the grid would hand back a
134 // different property than the one tabulated.
135 if (v.size() == 1 && x.size() <= 1)
136 return PropertyTable::constant(v[0]);
137 if (x.empty() && v.empty())
138 return PropertyTable();
139 throw std::invalid_argument(
140 std::string("PropertyTable: ") +
141 name +
142 " has a grid of " +
143 std::to_string(x.size()) +
144 " point(s) against " +
145 std::to_string(v.size()) +
146 " value(s). They must be the same length and at least 2 points, or a "
147 "single value for a constant, or both empty to leave the property unset.");
148 }
149
151 bool empty() const { return m_T.empty(); }
152
156 bool covers(double x) const
157 {
158 return !m_T.empty() && x >= m_T.front() && x <= m_T.back();
159 }
160
162 double first() const { return m_T.front(); }
163
165 double last() const { return m_T.back(); }
166
170 double eval(double T) const
171 {
172 if (m_T.empty())
173 {
174 return 0.0;
175 }
176 if (T <= m_T.front())
177 {
178 return m_v.front();
179 }
180 if (T >= m_T.back())
181 {
182 return m_v.back();
183 }
184 // upper_bound gives the first node strictly greater than T; subtract 1
185 // to get the left bracket of the containing interval.
186 auto it = std::upper_bound(m_T.begin(), m_T.end(), T);
187 size_t hi = static_cast<size_t>(it - m_T.begin());
188 size_t lo = hi - 1;
189 double w = (T - m_T[lo]) / (m_T[hi] - m_T[lo]);
190 return m_v[lo] + w * (m_v[hi] - m_v[lo]);
191 }
192
193 private:
194 std::vector<double> m_T;
195 std::vector<double> m_v;
196 };
197
198} // namespace rizer
199
200#endif
static PropertyTable constant(double value)
A two-point constant table returning value for any T.
double first() const
First abscissa of a non-empty table, in the table's own units.
double last() const
Last abscissa of a non-empty table, in the table's own units.
bool covers(double x) const
Whether eval() interpolates at x rather than holding an end value.
double eval(double T) const
Linearly interpolated value at T; clamped to the table endpoints.
bool empty() const
True for a default-constructed (empty) table; eval() returns 0.0.
PropertyTable(std::vector< double > T, std::vector< double > values)
Construct from parallel T (non-decreasing, [K]) and value arrays.
static PropertyTable fromTableOrScalar(const std::vector< double > &x, const std::vector< double > &v, const char *name="property table")
Build a table from a grid/value pair, a single scalar, or neither.