test: integrate shell thermal physics verification
This commit is contained in:
@@ -0,0 +1,70 @@
|
|||||||
|
// Generated by thermal_oracle_generator.py; exact root/weight certificates.
|
||||||
|
#ifndef FESA_TESTS_THERMAL_GAUSS_ORACLE_H_
|
||||||
|
#define FESA_TESTS_THERMAL_GAUSS_ORACLE_H_
|
||||||
|
#include <array>
|
||||||
|
namespace thermal_oracle {
|
||||||
|
struct GaussPoint {
|
||||||
|
double x_lo, x_hi, w_lo, w_hi;
|
||||||
|
};
|
||||||
|
inline constexpr std::array<GaussPoint, 12> kGauss12{{
|
||||||
|
{-0x1.f68f1d8e42e82p-1, -0x1.f68f1d8e42e80p-1, 0x1.8275d9de72d45p-5,
|
||||||
|
0x1.8275d9dedaddep-5},
|
||||||
|
{-0x1.cee874ffb88b5p-1, -0x1.cee874ffb88b3p-1, 0x1.b60602bcd3d56p-4,
|
||||||
|
0x1.b60602bcf85a4p-4},
|
||||||
|
{-0x1.8a30aeed88f37p-1, -0x1.8a30aeed88f35p-1, 0x1.47d7258f1fdcdp-3,
|
||||||
|
0x1.47d7258f25d51p-3},
|
||||||
|
{-0x1.2cb4f05c077fap-1, -0x1.2cb4f05c077f8p-1, 0x1.a0163e6b1a279p-3,
|
||||||
|
0x1.a0163e6b1b46ap-3},
|
||||||
|
{-0x1.78a8d20a8b19ep-2, -0x1.78a8d20a8b19cp-2, 0x1.de3155c256a18p-3,
|
||||||
|
0x1.de3155c256b52p-3},
|
||||||
|
{-0x1.007a5f8f630e5p-3, -0x1.007a5f8f630e3p-3, 0x1.fe40ce6d4f01bp-3,
|
||||||
|
0x1.fe40ce6d4f030p-3},
|
||||||
|
{0x1.007a5f8f630e3p-3, 0x1.007a5f8f630e5p-3, 0x1.fe40ce6d4f01bp-3,
|
||||||
|
0x1.fe40ce6d4f030p-3},
|
||||||
|
{0x1.78a8d20a8b19cp-2, 0x1.78a8d20a8b19ep-2, 0x1.de3155c256a18p-3,
|
||||||
|
0x1.de3155c256b52p-3},
|
||||||
|
{0x1.2cb4f05c077f8p-1, 0x1.2cb4f05c077fap-1, 0x1.a0163e6b1a279p-3,
|
||||||
|
0x1.a0163e6b1b46ap-3},
|
||||||
|
{0x1.8a30aeed88f35p-1, 0x1.8a30aeed88f37p-1, 0x1.47d7258f1fdcdp-3,
|
||||||
|
0x1.47d7258f25d51p-3},
|
||||||
|
{0x1.cee874ffb88b3p-1, 0x1.cee874ffb88b5p-1, 0x1.b60602bcd3d56p-4,
|
||||||
|
0x1.b60602bcf85a4p-4},
|
||||||
|
{0x1.f68f1d8e42e80p-1, 0x1.f68f1d8e42e82p-1, 0x1.8275d9de72d45p-5,
|
||||||
|
0x1.8275d9dedaddep-5},
|
||||||
|
}};
|
||||||
|
inline constexpr std::array<GaussPoint, 16> kGauss16{{
|
||||||
|
{-0x1.fa92c264d787fp-1, -0x1.fa92c264d787dp-1, 0x1.bcddab41dec5ap-6,
|
||||||
|
0x1.bcddab55197bep-6},
|
||||||
|
{-0x1.e39f56616f9b1p-1, -0x1.e39f56616f9afp-1, 0x1.fdfb1a27efbd3p-5,
|
||||||
|
0x1.fdfb1a30350e0p-5},
|
||||||
|
{-0x1.bb3403514e484p-1, -0x1.bb3403514e482p-1, 0x1.85c4ee78dfd1dp-4,
|
||||||
|
0x1.85c4ee7ab8792p-4},
|
||||||
|
{-0x1.82c45dda4726cp-1, -0x1.82c45dda4726ap-1, 0x1.fe7af2ba91fc7p-4,
|
||||||
|
0x1.fe7af2bb1510fp-4},
|
||||||
|
{-0x1.3c5a466d5e8b9p-1, -0x1.3c5a466d5e8b7p-1, 0x1.325f61bc9dd8dp-3,
|
||||||
|
0x1.325f61bca9bf1p-3},
|
||||||
|
{-0x1.d50259a43a773p-2, -0x1.d50259a43a771p-2, 0x1.5a6ebbb5a6fa2p-3,
|
||||||
|
0x1.5a6ebbb5a7c56p-3},
|
||||||
|
{-0x1.205cae642337dp-2, -0x1.205cae642337bp-2, 0x1.75f8c77e0bf66p-3,
|
||||||
|
0x1.75f8c77e0c0b8p-3},
|
||||||
|
{-0x1.852bd6676a9fap-4, -0x1.852bd6676a9f8p-4, 0x1.83feae80e4df6p-3,
|
||||||
|
0x1.83feae80e4e01p-3},
|
||||||
|
{0x1.852bd6676a9f8p-4, 0x1.852bd6676a9fap-4, 0x1.83feae80e4df6p-3,
|
||||||
|
0x1.83feae80e4e01p-3},
|
||||||
|
{0x1.205cae642337bp-2, 0x1.205cae642337dp-2, 0x1.75f8c77e0bf66p-3,
|
||||||
|
0x1.75f8c77e0c0b8p-3},
|
||||||
|
{0x1.d50259a43a771p-2, 0x1.d50259a43a773p-2, 0x1.5a6ebbb5a6fa2p-3,
|
||||||
|
0x1.5a6ebbb5a7c56p-3},
|
||||||
|
{0x1.3c5a466d5e8b7p-1, 0x1.3c5a466d5e8b9p-1, 0x1.325f61bc9dd8dp-3,
|
||||||
|
0x1.325f61bca9bf1p-3},
|
||||||
|
{0x1.82c45dda4726ap-1, 0x1.82c45dda4726cp-1, 0x1.fe7af2ba91fc7p-4,
|
||||||
|
0x1.fe7af2bb1510fp-4},
|
||||||
|
{0x1.bb3403514e482p-1, 0x1.bb3403514e484p-1, 0x1.85c4ee78dfd1dp-4,
|
||||||
|
0x1.85c4ee7ab8792p-4},
|
||||||
|
{0x1.e39f56616f9afp-1, 0x1.e39f56616f9b1p-1, 0x1.fdfb1a27efbd3p-5,
|
||||||
|
0x1.fdfb1a30350e0p-5},
|
||||||
|
{0x1.fa92c264d787dp-1, 0x1.fa92c264d787fp-1, 0x1.bcddab41dec5ap-6,
|
||||||
|
0x1.bcddab55197bep-6},
|
||||||
|
}};
|
||||||
|
} // namespace thermal_oracle
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
"""Generate test-only certified Gauss brackets from exact rational arithmetic.
|
||||||
|
|
||||||
|
Newton/Decimal produces candidates only. Exact Fraction signs prove n distinct
|
||||||
|
Legendre roots; rational interval recurrence encloses the associated weights.
|
||||||
|
No runtime solver dependency, reference modification, or libm ULP premise.
|
||||||
|
"""
|
||||||
|
from decimal import Decimal, localcontext
|
||||||
|
from fractions import Fraction as F
|
||||||
|
from pathlib import Path
|
||||||
|
import math
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
def add(a, b):
|
||||||
|
return a[0] + b[0], a[1] + b[1]
|
||||||
|
|
||||||
|
|
||||||
|
def neg(a):
|
||||||
|
return -a[1], -a[0]
|
||||||
|
|
||||||
|
|
||||||
|
def mul(a, b):
|
||||||
|
v = [x * y for x in a for y in b]
|
||||||
|
return min(v), max(v)
|
||||||
|
|
||||||
|
|
||||||
|
def div(a, b):
|
||||||
|
assert b[0] > 0 or b[1] < 0
|
||||||
|
return mul(a, (1 / b[1], 1 / b[0]))
|
||||||
|
|
||||||
|
|
||||||
|
def scale(a, b):
|
||||||
|
return mul(a, (F(b), F(b)))
|
||||||
|
|
||||||
|
|
||||||
|
def legendre(n, x):
|
||||||
|
p, q = (F(1), F(1)), x
|
||||||
|
for k in range(2, n + 1):
|
||||||
|
p, q = q, scale(add(scale(mul(x, q), 2 * k - 1), neg(scale(p, k - 1))), F(1, k))
|
||||||
|
derivative = div(scale(add(mul(x, q), neg(p)), n), add(mul(x, x), (-F(1), -F(1))))
|
||||||
|
return q, derivative
|
||||||
|
|
||||||
|
|
||||||
|
def bracket(value):
|
||||||
|
return math.nextafter(float(value[0]), -math.inf), math.nextafter(float(value[1]), math.inf)
|
||||||
|
|
||||||
|
|
||||||
|
def rule(n):
|
||||||
|
result = []
|
||||||
|
for i in range(n):
|
||||||
|
with localcontext() as context:
|
||||||
|
context.prec = 90
|
||||||
|
z = Decimal(str(math.cos(math.pi * (i + .75) / (n + .5))))
|
||||||
|
for _ in range(30):
|
||||||
|
p, q = Decimal(1), z
|
||||||
|
for k in range(2, n + 1):
|
||||||
|
p, q = q, ((2 * k - 1) * z * q - (k - 1) * p) / k
|
||||||
|
d = n * (z * q - p) / (z * z - 1)
|
||||||
|
z -= q / d
|
||||||
|
lo, hi = bracket((F(z), F(z)))
|
||||||
|
pl = legendre(n, (F(lo), F(lo)))[0][0]
|
||||||
|
ph = legendre(n, (F(hi), F(hi)))[0][0]
|
||||||
|
assert pl * ph < 0
|
||||||
|
x = F(lo), F(hi)
|
||||||
|
derivative = legendre(n, x)[1]
|
||||||
|
w = div((F(2), F(2)), mul(add((F(1), F(1)), neg(mul(x, x))), mul(derivative, derivative)))
|
||||||
|
result.append((lo, hi, *bracket(w)))
|
||||||
|
result.sort()
|
||||||
|
assert all(a[1] < b[0] for a, b in zip(result, result[1:]))
|
||||||
|
assert sum(F(r[2]) for r in result) < 2 < sum(F(r[3]) for r in result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def slab_fixtures():
|
||||||
|
# Approved Appendix A supplies the independently implemented Cholesky /
|
||||||
|
# Jacobi spectral candidates; Appendix E supplies certified transcendentals.
|
||||||
|
document = Path(__file__).resolve().parents[3] / "docs/shell-transient-heat-transfer/numerical-oracle-scratch.md"
|
||||||
|
blocks = re.findall(r"```python\n(.*?)```", document.read_text(encoding="utf-8-sig"), re.S)
|
||||||
|
spectral = {}
|
||||||
|
exec(blocks[0].split('out={"lambda"')[0], spectral)
|
||||||
|
interval_scope = {}
|
||||||
|
exec(blocks[-1].split("\nfor t in [.001")[0], interval_scope)
|
||||||
|
iv, ia, im, isc = (interval_scope[k] for k in ("interval", "add", "mul", "scale"))
|
||||||
|
ex = interval_scope["exneg"]
|
||||||
|
n = 4
|
||||||
|
|
||||||
|
def exact_inverse(matrix):
|
||||||
|
a = [list(row) + [F(i == j) for j in range(n)] for i, row in enumerate(matrix)]
|
||||||
|
for k in range(n):
|
||||||
|
pivot = next(i for i in range(k, n) if a[i][k])
|
||||||
|
a[k], a[pivot] = a[pivot], a[k]
|
||||||
|
divisor = a[k][k]
|
||||||
|
a[k] = [x / divisor for x in a[k]]
|
||||||
|
for i in range(n):
|
||||||
|
if i != k:
|
||||||
|
factor = a[i][k]
|
||||||
|
a[i] = [x - factor * y for x, y in zip(a[i], a[k])]
|
||||||
|
return [row[n:] for row in a]
|
||||||
|
|
||||||
|
def exact_mm(a, b):
|
||||||
|
return [[sum((x*y for x, y in zip(row, col)), F(0)) for col in zip(*b)] for row in a]
|
||||||
|
|
||||||
|
def matrix_multiply(a, b):
|
||||||
|
return [[sum_intervals([im(x, y) for x, y in zip(row, col)]) for col in zip(*b)] for row in a]
|
||||||
|
|
||||||
|
def sum_intervals(values):
|
||||||
|
total = iv(0)
|
||||||
|
for v in values:
|
||||||
|
total = ia(total, v)
|
||||||
|
return total
|
||||||
|
|
||||||
|
def apply(a, v):
|
||||||
|
return [sum_intervals([im(x, y) for x, y in zip(row, v)]) for row in a]
|
||||||
|
|
||||||
|
def power(a, count):
|
||||||
|
result = [[iv(i == j) for j in range(n)] for i in range(n)]
|
||||||
|
while count:
|
||||||
|
if count & 1:
|
||||||
|
result = matrix_multiply(result, a)
|
||||||
|
count >>= 1
|
||||||
|
if count:
|
||||||
|
a = matrix_multiply(a, a)
|
||||||
|
return result
|
||||||
|
|
||||||
|
c = [[F(0) for _ in range(5)] for _ in range(5)]
|
||||||
|
k = [[F(0) for _ in range(5)] for _ in range(5)]
|
||||||
|
for offset in (0, 2):
|
||||||
|
for i in range(3):
|
||||||
|
for j in range(3):
|
||||||
|
c[offset+i][offset+j] += F([[4, 2, -1], [2, 16, 2], [-1, 2, 4]][i][j], 60)
|
||||||
|
k[offset+i][offset+j] += F(2, 3)*[[7, -8, 1], [-8, 16, -8], [1, -8, 7]][i][j]
|
||||||
|
cf, kf = [r[1:] for r in c[1:]], [r[1:] for r in k[1:]]
|
||||||
|
inverse = exact_inverse(cf)
|
||||||
|
d = exact_mm(inverse, kf)
|
||||||
|
y = [-1-sum(inverse[i][j]*c[j+1][0] for j in range(n)) for i in range(n)]
|
||||||
|
|
||||||
|
def matrix_exponential(t):
|
||||||
|
# An independent enclosure of exp(-Cff^-1 Kff t) certifies all spectral
|
||||||
|
# eigenpair/coefficient errors together, rather than assuming libm ULPs.
|
||||||
|
# For ||B||inf<=1/2, the 64-term exponential remainder is bounded by
|
||||||
|
# 2*(1/2)^65/65!. Entrywise intervals preserve subsequent squarings.
|
||||||
|
magnitude = max(sum(abs(x*t) for x in row) for row in d)
|
||||||
|
shift = 0
|
||||||
|
while magnitude > F(1, 2):
|
||||||
|
magnitude /= 2
|
||||||
|
shift += 1
|
||||||
|
a = [[iv(-x*t/F(2)**shift) for x in row] for row in d]
|
||||||
|
term = [[iv(i == j) for j in range(n)] for i in range(n)]
|
||||||
|
result = [row[:] for row in term]
|
||||||
|
for order in range(1, 65):
|
||||||
|
term = [[isc(x, F(1, order)) for x in row] for row in matrix_multiply(term, a)]
|
||||||
|
result = [[ia(x, z) for x, z in zip(row, terms)] for row, terms in zip(result, term)]
|
||||||
|
remainder = 2*F(1, 2)**65/math.factorial(65)
|
||||||
|
result = [[ia(x, iv(-remainder, remainder)) for x in row] for row in result]
|
||||||
|
for _ in range(shift):
|
||||||
|
result = matrix_multiply(result, result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def outputs(error):
|
||||||
|
temperature = [iv(1)] + [ia(iv(1), z) for z in error]
|
||||||
|
derivative = [iv(0)] + apply([[iv(-x) for x in row] for row in d], error)
|
||||||
|
flux = []
|
||||||
|
for offset, s in ((0, -1), (0, 0), (0, 1), (2, -1), (2, 0), (2, 1)):
|
||||||
|
deriv = [F(4)*F(s-F(1, 2)), F(-8*s), F(4)*F(s+F(1, 2))]
|
||||||
|
flux.append(sum_intervals([isc(temperature[offset+i], -deriv[i]) for i in range(3)]))
|
||||||
|
energy = sum_intervals([isc(temperature[j], sum(c[i][j] for i in range(5))) for j in range(5)])
|
||||||
|
reaction = sum_intervals([isc(temperature[j], k[0][j]) for j in range(5)] +
|
||||||
|
[isc(derivative[j], c[0][j]) for j in range(5)])
|
||||||
|
return temperature + flux + [energy, reaction]
|
||||||
|
|
||||||
|
def spectral_candidate(t, count):
|
||||||
|
amplitudes = []
|
||||||
|
for eigenvalue, coefficient in zip(spectral["lam"], spectral["coef"]):
|
||||||
|
decay = ex(iv(F(eigenvalue)*t)) if count is None else power_scalar(
|
||||||
|
interval_scope["div"](iv(1), iv(1+F(eigenvalue)*t/count)), count)
|
||||||
|
amplitudes.append(isc(decay, F(coefficient)))
|
||||||
|
return outputs(apply([[iv(F(x)) for x in row] for row in spectral["W"]], amplitudes))
|
||||||
|
|
||||||
|
def power_scalar(x, count):
|
||||||
|
result = iv(1)
|
||||||
|
while count:
|
||||||
|
if count & 1:
|
||||||
|
result = im(result, x)
|
||||||
|
count >>= 1
|
||||||
|
if count:
|
||||||
|
x = im(x, x)
|
||||||
|
return result
|
||||||
|
|
||||||
|
table = [[.141, 10.7, .0304, 10.9], [.0153, 1.28, .00468, .824],
|
||||||
|
[.00598, .253, .00130, .00632], [.00139, .136, .000666, .00953],
|
||||||
|
[.000142, .0284, .0000945, .000526], [.0000414, .00816, .0000159, .0000479]]
|
||||||
|
fixtures, metrics = [], []
|
||||||
|
for time, space in zip((.001, .01, .05, .1, .5, 1.), table):
|
||||||
|
t = F(time)
|
||||||
|
temperatures, flux, energy, _, _ = interval_scope["series_interval"](time)
|
||||||
|
continuum = temperatures + [flux[i] for i in (0, 1, 2, 2, 3, 4)] + [energy, flux[0]]
|
||||||
|
solutions = []
|
||||||
|
spectral_enclosure = 0.
|
||||||
|
for count in (None, 4096, 8192):
|
||||||
|
if count is None:
|
||||||
|
operator = matrix_exponential(t)
|
||||||
|
else:
|
||||||
|
a = [[cf[i][j]+t/count*kf[i][j] for j in range(n)] for i in range(n)]
|
||||||
|
operator = power([[iv(x) for x in row] for row in exact_mm(exact_inverse(a), cf)], count)
|
||||||
|
exact = outputs(apply(operator, [iv(x) for x in y]))
|
||||||
|
candidate = spectral_candidate(t, count)
|
||||||
|
enclosed = [(min(a[0], b[0]), max(a[1], b[1])) for a, b in zip(exact, candidate)]
|
||||||
|
spectral_enclosure = max(spectral_enclosure, max(float(hi-lo) for lo, hi in enclosed))
|
||||||
|
solutions.append(enclosed)
|
||||||
|
fixtures.append((time, space, continuum, *solutions))
|
||||||
|
metrics.append({"time": time, "spectral_and_matrix_function_enclosure": spectral_enclosure})
|
||||||
|
print(json.dumps({"eigenvalues": spectral["lam"], "slab_oracle_metrics": metrics}, indent=2))
|
||||||
|
return fixtures
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
output = ["// Generated by thermal_oracle_generator.py; exact root/weight certificates.",
|
||||||
|
"#ifndef FESA_TESTS_THERMAL_GAUSS_ORACLE_H_", "#define FESA_TESTS_THERMAL_GAUSS_ORACLE_H_",
|
||||||
|
"#include <array>", "namespace thermal_oracle {",
|
||||||
|
"struct GaussPoint { double x_lo,x_hi,w_lo,w_hi; };"]
|
||||||
|
for n in (12, 16):
|
||||||
|
rows = rule(n)
|
||||||
|
output.append(f"inline constexpr std::array<GaussPoint,{n}> kGauss{n}{{{{")
|
||||||
|
output.extend(" {" + ",".join(x.hex() for x in row) + "}," for row in rows)
|
||||||
|
output.append("}};")
|
||||||
|
output.extend(["}", "#endif", ""])
|
||||||
|
destination = Path(__file__).with_name("thermal_gauss_oracle.h")
|
||||||
|
destination.write_text("\n".join(output), encoding="utf-8")
|
||||||
|
print("Certified Legendre roots/weights: 12 + 16 distinct roots;", destination.name)
|
||||||
|
fixtures = slab_fixtures()
|
||||||
|
output = ["// Generated by thermal_oracle_generator.py. Outward binary64 brackets.",
|
||||||
|
"#ifndef FESA_TESTS_THERMAL_SLAB_ORACLE_H_", "#define FESA_TESTS_THERMAL_SLAB_ORACLE_H_",
|
||||||
|
"#include <array>", "namespace thermal_oracle {",
|
||||||
|
"struct Bracket { double lo,hi; };",
|
||||||
|
"struct SlabCase { double time; std::array<double,4> space; std::array<Bracket,13> fourier,continuous,be4096,be8192; };",
|
||||||
|
"inline constexpr std::array<SlabCase,6> kSlab{{"]
|
||||||
|
for time, space, *values in fixtures:
|
||||||
|
output.append("{" + time.hex() + ",{{" + ",".join(x.hex() for x in space) + "}},")
|
||||||
|
for rows in values:
|
||||||
|
output.append("{{" + ",".join("{" + ",".join(x.hex() for x in bracket(row)) + "}" for row in rows) + "}},")
|
||||||
|
output.append("},")
|
||||||
|
output.extend(["}};", "}", "#endif", ""])
|
||||||
|
Path(__file__).with_name("thermal_slab_oracle.h").write_text("\n".join(output), encoding="utf-8")
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,359 @@
|
|||||||
|
// Generated by thermal_oracle_generator.py. Outward binary64 brackets.
|
||||||
|
#ifndef FESA_TESTS_THERMAL_SLAB_ORACLE_H_
|
||||||
|
#define FESA_TESTS_THERMAL_SLAB_ORACLE_H_
|
||||||
|
#include <array>
|
||||||
|
namespace thermal_oracle {
|
||||||
|
struct Bracket {
|
||||||
|
double lo, hi;
|
||||||
|
};
|
||||||
|
struct SlabCase {
|
||||||
|
double time;
|
||||||
|
std::array<double, 4> space;
|
||||||
|
std::array<Bracket, 13> fourier, continuous, be4096, be8192;
|
||||||
|
};
|
||||||
|
inline constexpr std::array<SlabCase, 6> kSlab{{
|
||||||
|
{
|
||||||
|
0x1.0624dd2f1a9fcp-10,
|
||||||
|
{{0x1.20c49ba5e353fp-3, 0x1.5666666666666p+3, 0x1.f212d77318fc5p-6,
|
||||||
|
0x1.5cccccccccccdp+3}},
|
||||||
|
{{{0x1.fffffffffffffp-1, 0x1.0000000000001p+0},
|
||||||
|
{0x1.85b89453c09acp-26, 0x1.85b89453c09aep-26},
|
||||||
|
{0x1.fe4543738e03ap-95, 0x1.0ff4c2f026ce0p-94},
|
||||||
|
{-0x1.64845561fdc38p-101, 0x1.c12311737a124p-99},
|
||||||
|
{-0x1.68eca14e83537p-101, 0x1.c008fe7858ae5p-99},
|
||||||
|
{0x1.1d75b94accf81p+4, 0x1.1d75b94accf83p+4},
|
||||||
|
{0x1.88168e094c2d6p-19, 0x1.88168e094c2d8p-19},
|
||||||
|
{0x1.e433884766565p-87, 0x1.02b7b8a2bf31ap-86},
|
||||||
|
{0x1.e433884766565p-87, 0x1.02b7b8a2bf31ap-86},
|
||||||
|
{-0x1.3a94ff682132fp-91, 0x1.b25320f2bf337p-92},
|
||||||
|
{-0x1.09df47f0c0666p-91, 0x1.09df47f0c0666p-91},
|
||||||
|
{0x1.244f96d3c38d6p-5, 0x1.244f96d3c38d8p-5},
|
||||||
|
{0x1.1d75b94accf81p+4, 0x1.1d75b94accf83p+4}}},
|
||||||
|
{{{0x1.fffffffffffffp-1, 0x1.0000000000001p+0},
|
||||||
|
{-0x1.d37c7f414f866p-4, -0x1.d37c7f414f859p-4},
|
||||||
|
{0x1.1eb81bc756156p-3, 0x1.1eb81bc75615ap-3},
|
||||||
|
{-0x1.17e5fa341d20bp-6, -0x1.17e5fa341d110p-6},
|
||||||
|
{0x1.2d98feedf9757p-5, 0x1.2d98feedf9788p-5},
|
||||||
|
{0x1.cc5b11a49f520p+2, 0x1.cc5b11a49f523p+2},
|
||||||
|
{0x1.b851f90e2a7a9p+0, 0x1.b851f90e2a7abp+0},
|
||||||
|
{-0x1.e0642a3b1429cp+1, -0x1.e0642a3b14296p+1},
|
||||||
|
{0x1.0ce0640ae3c9bp+0, 0x1.0ce0640ae3cbbp+0},
|
||||||
|
{0x1.a6a3b817af6eap-3, 0x1.a6a3b817af708p-3},
|
||||||
|
{-0x1.466eec09efdf1p-1, -0x1.466eec09efdc2p-1},
|
||||||
|
{0x1.0e51bf4794d1ep-4, 0x1.0e51bf4794d3ap-4},
|
||||||
|
{0x1.be34bece7dbcbp+2, 0x1.be34bece7dbd0p+2}}},
|
||||||
|
{{{0x1.fffffffffffffp-1, 0x1.0000000000001p+0},
|
||||||
|
{-0x1.d37cd7bcda5f0p-4, -0x1.d37cd7bcda5e4p-4},
|
||||||
|
{0x1.1eb8818ac9073p-3, 0x1.1eb8818ac9077p-3},
|
||||||
|
{-0x1.17e77bedcde83p-6, -0x1.17e77bedcdd88p-6},
|
||||||
|
{0x1.2d9a6a607059ep-5, 0x1.2d9a6a60705d0p-5},
|
||||||
|
{0x1.cc5b231047dc3p+2, 0x1.cc5b231047dc6p+2},
|
||||||
|
{0x1.b851df9d4dbe2p+0, 0x1.b851df9d4dbe4p+0},
|
||||||
|
{-0x1.e064668341fa9p+1, -0x1.e064668341fa4p+1},
|
||||||
|
{0x1.0ce0f74bd7864p+0, 0x1.0ce0f74bd7884p+0},
|
||||||
|
{0x1.a6a3cde559e00p-3, 0x1.a6a3cde559e1dp-3},
|
||||||
|
{-0x1.467007a5021f8p-1, -0x1.467007a5021c9p-1},
|
||||||
|
{0x1.0e51b2b4d8688p-4, 0x1.0e51b2b4d86a3p-4},
|
||||||
|
{0x1.be34d80c5223ap+2, 0x1.be34d80c5223fp+2}}},
|
||||||
|
{{{0x1.fffffffffffffp-1, 0x1.0000000000001p+0},
|
||||||
|
{-0x1.d37cab7f333f2p-4, -0x1.d37cab7f333e5p-4},
|
||||||
|
{0x1.1eb84ea93c6aep-3, 0x1.1eb84ea93c6b2p-3},
|
||||||
|
{-0x1.17e6bb11c7d2ep-6, -0x1.17e6bb11c7c33p-6},
|
||||||
|
{0x1.2d99b4a80421ep-5, 0x1.2d99b4a80424fp-5},
|
||||||
|
{0x1.cc5b1a5a7a2e7p+2, 0x1.cc5b1a5a7a2eap+2},
|
||||||
|
{0x1.b851ec55b0e53p+0, 0x1.b851ec55b0e55p+0},
|
||||||
|
{-0x1.e064485f43780p+1, -0x1.e064485f4377ap+1},
|
||||||
|
{0x1.0ce0adaba68aep+0, 0x1.0ce0adaba68cdp+0},
|
||||||
|
{0x1.a6a3c2fe76c36p-3, 0x1.a6a3c2fe76c53p-3},
|
||||||
|
{-0x1.466f79d811b70p-1, -0x1.466f79d811b41p-1},
|
||||||
|
{0x1.0e51b8fe3293cp-4, 0x1.0e51b8fe32958p-4},
|
||||||
|
{0x1.be34cb6d72726p+2, 0x1.be34cb6d7272bp+2}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
0x1.47ae147ae147bp-7,
|
||||||
|
{{0x1.f559b3d07c84bp-7, 0x1.47ae147ae147bp+0, 0x1.32b55ef1fddecp-8,
|
||||||
|
0x1.a5e353f7ced91p-1}},
|
||||||
|
{{{0x1.fffffffffffffp-1, 0x1.0000000000001p+0},
|
||||||
|
{0x1.3bcd133aa0ffbp-4, 0x1.3bcd133aa0ffdp-4},
|
||||||
|
{0x1.aab859b20ac9ep-12, 0x1.aab859b20aca0p-12},
|
||||||
|
{0x1.e87470e505fc1p-24, 0x1.e87470e505fc3p-24},
|
||||||
|
{0x1.b0c1a759f773cp-39, 0x1.b0c1a759f773ep-39},
|
||||||
|
{0x1.6914d24534247p+2, 0x1.6914d24534249p+2},
|
||||||
|
{0x1.2ebf3dcc9f22ep+0, 0x1.2ebf3dcc9f230p+0},
|
||||||
|
{0x1.64e3dcd3af4bap-7, 0x1.64e3dcd3af4bcp-7},
|
||||||
|
{0x1.64e3dcd3af4bap-7, 0x1.64e3dcd3af4bcp-7},
|
||||||
|
{0x1.27c272dde935ep-18, 0x1.27c272dde9360p-18},
|
||||||
|
{-0x1.64c3500000001p-300, 0x1.64c3500000001p-300},
|
||||||
|
{0x1.ce2f21a042be1p-4, 0x1.ce2f21a042be3p-4},
|
||||||
|
{0x1.6914d24534247p+2, 0x1.6914d24534249p+2}}},
|
||||||
|
{{{0x1.fffffffffffffp-1, 0x1.0000000000001p+0},
|
||||||
|
{0x1.7a435a2984e32p-4, 0x1.7a435a2984e3ap-4},
|
||||||
|
{0x1.9dc400057caf6p-12, 0x1.9dc400057d765p-12},
|
||||||
|
{0x1.ad9feeec8c48dp-7, 0x1.ad9feeec8c649p-7},
|
||||||
|
{-0x1.986d06319bd56p-7, -0x1.986d06319bcd8p-7},
|
||||||
|
{0x1.50c482dacf8f7p+2, 0x1.50c482dacf8f9p+2},
|
||||||
|
{0x1.ffcc477fff504p+0, 0x1.ffcc477fff508p+0},
|
||||||
|
{-0x1.43797c6b3f9d5p+0, -0x1.43797c6b3f9d2p+0},
|
||||||
|
{-0x1.05044c3c6933ap-3, -0x1.05044c3c69290p-3},
|
||||||
|
{0x1.a55b2631c7b93p-6, 0x1.a55b2631c7baep-6},
|
||||||
|
{0x1.6e5b15c8db17bp-3, 0x1.6e5b15c8db21fp-3},
|
||||||
|
{0x1.e15645f1d96e9p-4, 0x1.e15645f1d9703p-4},
|
||||||
|
{0x1.345d88b145b6ap+2, 0x1.345d88b145b6ep+2}}},
|
||||||
|
{{{0x1.fffffffffffffp-1, 0x1.0000000000001p+0},
|
||||||
|
{0x1.7a374829b1404p-4, 0x1.7a374829b140cp-4},
|
||||||
|
{0x1.af3900931af0ap-12, 0x1.af3900931bb78p-12},
|
||||||
|
{0x1.ad81aefddf88dp-7, 0x1.ad81aefddfa49p-7},
|
||||||
|
{-0x1.983e6ad119e83p-7, -0x1.983e6ad119e05p-7},
|
||||||
|
{0x1.50c690c2ce70cp+2, 0x1.50c690c2ce70ep+2},
|
||||||
|
{0x1.ffca18dfed9c8p+0, 0x1.ffca18dfed9cbp+0},
|
||||||
|
{-0x1.4386114b5e8a2p+0, -0x1.4386114b5e89ep+0},
|
||||||
|
{-0x1.04baf9d759bb3p-3, -0x1.04baf9d759b09p-3},
|
||||||
|
{0x1.a5b832d5b2be1p-6, 0x1.a5b832d5b2bfcp-6},
|
||||||
|
{0x1.6e29068cc6608p-3, 0x1.6e29068cc66acp-3},
|
||||||
|
{0x1.e154626664a52p-4, 0x1.e154626664a6cp-4},
|
||||||
|
{0x1.34600de90e320p+2, 0x1.34600de90e323p+2}}},
|
||||||
|
{{{0x1.fffffffffffffp-1, 0x1.0000000000001p+0},
|
||||||
|
{0x1.7a3d511a6a009p-4, 0x1.7a3d511a6a012p-4},
|
||||||
|
{0x1.a67e9a3b3c161p-12, 0x1.a67e9a3b3cdd0p-12},
|
||||||
|
{0x1.ad90cec9c0f6fp-7, 0x1.ad90cec9c112bp-7},
|
||||||
|
{-0x1.9855b85b10049p-7, -0x1.9855b85b0ffcbp-7},
|
||||||
|
{0x1.50c589d18499bp+2, 0x1.50c589d18499ep+2},
|
||||||
|
{0x1.ffcb302cb8985p+0, 0x1.ffcb302cb8989p+0},
|
||||||
|
{-0x1.437fc6eca1366p+0, -0x1.437fc6eca1362p+0},
|
||||||
|
{-0x1.04dfa2a190d26p-3, -0x1.04dfa2a190c7cp-3},
|
||||||
|
{0x1.a589ad2ce9e39p-6, 0x1.a589ad2ce9e54p-6},
|
||||||
|
{0x1.6e420deccb411p-3, 0x1.6e420deccb4b5p-3},
|
||||||
|
{0x1.e1555429f7bf1p-4, 0x1.e1555429f7c0bp-4},
|
||||||
|
{0x1.345ecb5088dc7p+2, 0x1.345ecb5088dcbp+2}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
0x1.999999999999ap-5,
|
||||||
|
{{0x1.87e7c06e19b91p-8, 0x1.03126e978d4fep-2, 0x1.54c985f06f694p-10,
|
||||||
|
0x1.9e30014f8b589p-8}},
|
||||||
|
{{{0x1.fffffffffffffp-1, 0x1.0000000000001p+0},
|
||||||
|
{0x1.b77efb25b8dd2p-2, 0x1.b77efb25b8dd4p-2},
|
||||||
|
{0x1.d252b304ed681p-4, 0x1.d252b304ed683p-4},
|
||||||
|
{0x1.235c8909400c5p-6, 0x1.235c8909400c7p-6},
|
||||||
|
{0x1.9a5c5e03484dep-9, 0x1.9a5c5e03484e0p-9},
|
||||||
|
{0x1.42f6019270650p+1, 0x1.42f6019270652p+1},
|
||||||
|
{0x1.d89101a46e842p+0, 0x1.d89101a46e844p+0},
|
||||||
|
{0x1.721a47b716779p-1, 0x1.721a47b71677bp-1},
|
||||||
|
{0x1.721a47b716779p-1, 0x1.721a47b71677bp-1},
|
||||||
|
{0x1.343beb6102cc6p-3, 0x1.343beb6102cc8p-3},
|
||||||
|
{-0x1.3f24600000001p-301, 0x1.3f24600000001p-301},
|
||||||
|
{0x1.025e67b9cdad7p-2, 0x1.025e67b9cdad9p-2},
|
||||||
|
{0x1.42f6019270650p+1, 0x1.42f6019270652p+1}}},
|
||||||
|
{{{0x1.fffffffffffffp-1, 0x1.0000000000001p+0},
|
||||||
|
{0x1.b8566cd59dd36p-2, 0x1.b8566cd59dd3ap-2},
|
||||||
|
{0x1.b9d5c5e3870abp-4, 0x1.b9d5c5e3870c1p-4},
|
||||||
|
{0x1.1587ab02cfe81p-6, 0x1.1587ab02cff38p-6},
|
||||||
|
{0x1.1ad4cd8131fbap-7, 0x1.1ad4cd8131fd0p-7},
|
||||||
|
{0x1.6346ef889a9d2p+1, 0x1.6346ef889a9d4p+1},
|
||||||
|
{0x1.c8c547438f1e7p+0, 0x1.c8c547438f1ecp+0},
|
||||||
|
{0x1.95f95eebd2052p-1, 0x1.95f95eebd205ep-1},
|
||||||
|
{0x1.0ed51015fadbfp-1, 0x1.0ed51015fade0p-1},
|
||||||
|
{0x1.967b2c3360cb1p-3, 0x1.967b2c3360cc9p-3},
|
||||||
|
{-0x1.0e5de7f129e18p-3, -0x1.0e5de7f129d6ep-3},
|
||||||
|
{0x1.010a6df46a07bp-2, 0x1.010a6df46a082p-2},
|
||||||
|
{0x1.43c4ee9a8ce2bp+1, 0x1.43c4ee9a8ce2fp+1}}},
|
||||||
|
{{{0x1.fffffffffffffp-1, 0x1.0000000000001p+0},
|
||||||
|
{0x1.b84e9bd4911f6p-2, 0x1.b84e9bd4911fap-2},
|
||||||
|
{0x1.b9ccf55bd5095p-4, 0x1.b9ccf55bd50abp-4},
|
||||||
|
{0x1.15b1a7159d30ap-6, 0x1.15b1a7159d3c1p-6},
|
||||||
|
{0x1.1b178ee6bb5d6p-7, 0x1.1b178ee6bb5ecp-7},
|
||||||
|
{0x1.634e33812c311p+1, 0x1.634e33812c313p+1},
|
||||||
|
{0x1.c8c66154855eap+0, 0x1.c8c66154855eep+0},
|
||||||
|
{0x1.95e0b74d64b63p-1, 0x1.95e0b74d64b6ep-1},
|
||||||
|
{0x1.0ec60ab6ae53dp-1, 0x1.0ec60ab6ae55ep-1},
|
||||||
|
{0x1.966a037efd9d7p-3, 0x1.966a037efd9f0p-3},
|
||||||
|
{-0x1.0e4423dcbe1c4p-3, -0x1.0e4423dcbe11ap-3},
|
||||||
|
{0x1.0108815a6658cp-2, 0x1.0108815a66593p-2},
|
||||||
|
{0x1.43cb51041b9e3p+1, 0x1.43cb51041b9e7p+1}}},
|
||||||
|
{{{0x1.fffffffffffffp-1, 0x1.0000000000001p+0},
|
||||||
|
{0x1.b85284545e3fep-2, 0x1.b85284545e402p-2},
|
||||||
|
{0x1.b9d15d4ee5b37p-4, 0x1.b9d15d4ee5b4dp-4},
|
||||||
|
{0x1.159ca92438c35p-6, 0x1.159ca92438cebp-6},
|
||||||
|
{0x1.1af6311dbcc58p-7, 0x1.1af6311dbcc6ep-7},
|
||||||
|
{0x1.634a9180901b3p+1, 0x1.634a9180901b5p+1},
|
||||||
|
{0x1.c8c5d45623495p+0, 0x1.c8c5d4562349ap+0},
|
||||||
|
{0x1.95ed0b564cb88p-1, 0x1.95ed0b564cb94p-1},
|
||||||
|
{0x1.0ecd8d3b0bfa0p-1, 0x1.0ecd8d3b0bfc1p-1},
|
||||||
|
{0x1.9672972b2e1aap-3, 0x1.9672972b2e1c2p-3},
|
||||||
|
{-0x1.0e510695d3bacp-3, -0x1.0e510695d3b02p-3},
|
||||||
|
{0x1.010977a63dfebp-2, 0x1.010977a63dff1p-2},
|
||||||
|
{0x1.43c81fc9071c0p+1, 0x1.43c81fc9071c4p+1}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
0x1.999999999999ap-4,
|
||||||
|
{{0x1.6c61522a6f3f5p-10, 0x1.16872b020c49cp-3, 0x1.5d2d01c0ca601p-11,
|
||||||
|
0x1.38476f2a5a46ap-7}},
|
||||||
|
{{{0x1.fffffffffffffp-1, 0x1.0000000000001p+0},
|
||||||
|
{0x1.270906ee5a6cfp-1, 0x1.270906ee5a6d1p-1},
|
||||||
|
{0x1.0eb16bef11388p-2, 0x1.0eb16bef1138ap-2},
|
||||||
|
{0x1.945c98b83c928p-4, 0x1.945c98b83c92ap-4},
|
||||||
|
{0x1.9f4a5c2b49fb7p-5, 0x1.9f4a5c2b49fb9p-5},
|
||||||
|
{0x1.c8b1bdc9d3f27p+0, 0x1.c8b1bdc9d3f29p+0},
|
||||||
|
{0x1.8673042a10047p+0, 0x1.8673042a10049p+0},
|
||||||
|
{0x1.e5a6c4f8749dfp-1, 0x1.e5a6c4f8749e1p-1},
|
||||||
|
{0x1.e5a6c4f8749dfp-1, 0x1.e5a6c4f8749e1p-1},
|
||||||
|
{0x1.9af613adf14c0p-2, 0x1.9af613adf14c2p-2},
|
||||||
|
{-0x1.c357400000001p-302, 0x1.c357400000001p-302},
|
||||||
|
{0x1.6d631d0d902e4p-2, 0x1.6d631d0d902e6p-2},
|
||||||
|
{0x1.c8b1bdc9d3f27p+0, 0x1.c8b1bdc9d3f29p+0}}},
|
||||||
|
{{{0x1.fffffffffffffp-1, 0x1.0000000000001p+0},
|
||||||
|
{0x1.26f65a0fd4a73p-1, 0x1.26f65a0fd4a75p-1},
|
||||||
|
{0x1.0e6d0af83d0acp-2, 0x1.0e6d0af83d0b2p-2},
|
||||||
|
{0x1.916bd5fe070d9p-4, 0x1.916bd5fe07101p-4},
|
||||||
|
{0x1.93ecedc1f0d7fp-5, 0x1.93ecedc1f0d89p-5},
|
||||||
|
{0x1.eb5d1d3ccbe86p+0, 0x1.eb5d1d3ccbe89p+0},
|
||||||
|
{0x1.78c97a83e17a7p+0, 0x1.78c97a83e17aap+0},
|
||||||
|
{0x1.0635d7caf70c6p+0, 0x1.0635d7caf70cdp+0},
|
||||||
|
{0x1.cc58e8a2ee2c2p-1, 0x1.cc58e8a2ee2dfp-1},
|
||||||
|
{0x1.b7deda7ffddf7p-2, 0x1.b7deda7ffde03p-2},
|
||||||
|
{-0x1.47a0e22f04e5ep-5, -0x1.47a0e22f04c0dp-5},
|
||||||
|
{0x1.6cb487831db4ap-2, 0x1.6cb487831db50p-2},
|
||||||
|
{0x1.cb21e9c5f325fp+0, 0x1.cb21e9c5f3263p+0}}},
|
||||||
|
{{{0x1.fffffffffffffp-1, 0x1.0000000000001p+0},
|
||||||
|
{0x1.26f24a61a564bp-1, 0x1.26f24a61a564dp-1},
|
||||||
|
{0x1.0e667df8508a0p-2, 0x1.0e667df8508a6p-2},
|
||||||
|
{0x1.9170e359fb821p-4, 0x1.9170e359fb849p-4},
|
||||||
|
{0x1.9417f976522f8p-5, 0x1.9417f97652303p-5},
|
||||||
|
{0x1.eb6a157592b20p+0, 0x1.eb6a157592b23p+0},
|
||||||
|
{0x1.78ccc103d7badp+0, 0x1.78ccc103d7bb1p+0},
|
||||||
|
{0x1.062f6c921cc39p+0, 0x1.062f6c921cc3fp+0},
|
||||||
|
{0x1.cc4595bdc0604p-1, 0x1.cc4595bdc0621p-1},
|
||||||
|
{0x1.b7c6fd930c880p-2, 0x1.b7c6fd930c88cp-2},
|
||||||
|
{-0x1.47e982ab3d9f7p-5, -0x1.47e982ab3d7a5p-5},
|
||||||
|
{0x1.6cb19975e1802p-2, 0x1.6cb19975e1808p-2},
|
||||||
|
{0x1.cb2cb028081f8p+0, 0x1.cb2cb028081fcp+0}}},
|
||||||
|
{{{0x1.fffffffffffffp-1, 0x1.0000000000001p+0},
|
||||||
|
{0x1.26f45238988fdp-1, 0x1.26f45238988ffp-1},
|
||||||
|
{0x1.0e69c46af3672p-2, 0x1.0e69c46af3678p-2},
|
||||||
|
{0x1.916e5ca776061p-4, 0x1.916e5ca776088p-4},
|
||||||
|
{0x1.940274204c9cdp-5, 0x1.940274204c9d8p-5},
|
||||||
|
{0x1.eb63995317741p+0, 0x1.eb63995317743p+0},
|
||||||
|
{0x1.78cb1dca864c4p+0, 0x1.78cb1dca864c8p+0},
|
||||||
|
{0x1.0632a241f5246p+0, 0x1.0632a241f524cp+0},
|
||||||
|
{0x1.cc4f3f1d6dc15p-1, 0x1.cc4f3f1d6dc32p-1},
|
||||||
|
{0x1.b7d2ebcdd3a6fp-2, 0x1.b7d2ebcdd3a7bp-2},
|
||||||
|
{-0x1.47c534f9a1c17p-5, -0x1.47c534f9a19c6p-5},
|
||||||
|
{0x1.6cb3107b2e3dep-2, 0x1.6cb3107b2e3e4p-2},
|
||||||
|
{0x1.cb274cef49caep+0, 0x1.cb274cef49cb1p+0}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
0x1.0000000000000p-1,
|
||||||
|
{{0x1.29cbab649d389p-13, 0x1.d14e3bcd35a86p-6, 0x1.8c5c9a34ca0c3p-14,
|
||||||
|
0x1.13c68661ae70cp-11}},
|
||||||
|
{{{0x1.fffffffffffffp-1, 0x1.0000000000001p+0},
|
||||||
|
{0x1.b7590ca5d56abp-1, 0x1.b7590ca5d56adp-1},
|
||||||
|
{0x1.79c27556059fcp-1, 0x1.79c27556059fep-1},
|
||||||
|
{0x1.509c59ccb1964p-1, 0x1.509c59ccb1966p-1},
|
||||||
|
{0x1.422975f1d50c3p-1, 0x1.422975f1d50c5p-1},
|
||||||
|
{0x1.2a37abf469800p-1, 0x1.2a37abf469802p-1},
|
||||||
|
{0x1.1382380277aa0p-1, 0x1.1382380277aa2p-1},
|
||||||
|
{0x1.a5b316788e428p-2, 0x1.a5b316788e42ap-2},
|
||||||
|
{0x1.a5b316788e428p-2, 0x1.a5b316788e42ap-2},
|
||||||
|
{0x1.c86932802133fp-3, 0x1.c869328021341p-3},
|
||||||
|
{-0x1.26ee800000001p-303, 0x1.26ee800000001p-303},
|
||||||
|
{0x1.87247f6ca05d8p-1, 0x1.87247f6ca05dap-1},
|
||||||
|
{0x1.2a37abf469800p-1, 0x1.2a37abf469802p-1}}},
|
||||||
|
{{{0x1.fffffffffffffp-1, 0x1.0000000000001p+0},
|
||||||
|
{0x1.b7553624e5316p-1, 0x1.b7553624e5318p-1},
|
||||||
|
{0x1.79b5b3c71720bp-1, 0x1.79b5b3c71720dp-1},
|
||||||
|
{0x1.509288faf814fp-1, 0x1.509288faf8152p-1},
|
||||||
|
{0x1.4216f7b692db3p-1, 0x1.4216f7b692db6p-1},
|
||||||
|
{0x1.38c1b66704b5dp-1, 0x1.38c1b66704b63p-1},
|
||||||
|
{0x1.0c949871d1be7p-1, 0x1.0c949871d1be9p-1},
|
||||||
|
{0x1.c0cef4f93d8ddp-2, 0x1.c0cef4f93d8e5p-2},
|
||||||
|
{0x1.b3b7bc7fdfa53p-2, 0x1.b3b7bc7fdfa69p-2},
|
||||||
|
{0x1.bcf5e084222b4p-3, 0x1.bcf5e084222bdp-3},
|
||||||
|
{0x1.27c480885099dp-7, 0x1.27c4808850cf9p-7},
|
||||||
|
{0x1.87181cfb04848p-1, 0x1.87181cfb0484ap-1},
|
||||||
|
{0x1.2a7c94e9a3a3dp-1, 0x1.2a7c94e9a3a41p-1}}},
|
||||||
|
{{{0x1.fffffffffffffp-1, 0x1.0000000000001p+0},
|
||||||
|
{0x1.b751be282259dp-1, 0x1.b751be28225a0p-1},
|
||||||
|
{0x1.79af4d5fe0284p-1, 0x1.79af4d5fe0286p-1},
|
||||||
|
{0x1.508a2ff978465p-1, 0x1.508a2ff978468p-1},
|
||||||
|
{0x1.420defb8a1c5ap-1, 0x1.420defb8a1c5dp-1},
|
||||||
|
{0x1.38d0a97ead811p-1, 0x1.38d0a97ead816p-1},
|
||||||
|
{0x1.0ca165403faf5p-1, 0x1.0ca165403faf8p-1},
|
||||||
|
{0x1.c0e44203a3bafp-2, 0x1.c0e44203a3bb7p-2},
|
||||||
|
{0x1.b3cc5fc984930p-2, 0x1.b3cc5fc984946p-2},
|
||||||
|
{0x1.bd0aed39f3147p-3, 0x1.bd0aed39f3150p-3},
|
||||||
|
{0x1.27d1ae0dd005dp-7, 0x1.27d1ae0dd03b9p-7},
|
||||||
|
{0x1.87125aea910c9p-1, 0x1.87125aea910cbp-1},
|
||||||
|
{0x1.2a8ad7437b2aep-1, 0x1.2a8ad7437b2b1p-1}}},
|
||||||
|
{{{0x1.fffffffffffffp-1, 0x1.0000000000001p+0},
|
||||||
|
{0x1.b7537a2158170p-1, 0x1.b7537a2158173p-1},
|
||||||
|
{0x1.79b28088e2c65p-1, 0x1.79b28088e2c67p-1},
|
||||||
|
{0x1.508e5c6b105ecp-1, 0x1.508e5c6b105efp-1},
|
||||||
|
{0x1.421273a69f88ep-1, 0x1.421273a69f891p-1},
|
||||||
|
{0x1.38c9300704d3bp-1, 0x1.38c9300704d40p-1},
|
||||||
|
{0x1.0c9afeee3a733p-1, 0x1.0c9afeee3a736p-1},
|
||||||
|
{0x1.c0d99baae0253p-2, 0x1.c0d99baae025bp-2},
|
||||||
|
{0x1.b3c20e5419820p-2, 0x1.b3c20e5419836p-2},
|
||||||
|
{0x1.bd00671219eacp-3, 0x1.bd00671219eb4p-3},
|
||||||
|
{0x1.27cb17c00cee7p-7, 0x1.27cb17c00d243p-7},
|
||||||
|
{0x1.87153be8d5e91p-1, 0x1.87153be8d5e93p-1},
|
||||||
|
{0x1.2a83b62aa8393p-1, 0x1.2a83b62aa8396p-1}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
0x1.0000000000000p+0,
|
||||||
|
{{0x1.5b49d2b1e91bbp-15, 0x1.0b630a91537a0p-7, 0x1.0ac1fae1b30dep-16,
|
||||||
|
0x1.91d0777c385fcp-15}},
|
||||||
|
{{{0x1.fffffffffffffp-1, 0x1.0000000000001p+0},
|
||||||
|
{0x1.ead7f87070237p-1, 0x1.ead7f87070239p-1},
|
||||||
|
{0x1.d8e87b7ae7a62p-1, 0x1.d8e87b7ae7a64p-1},
|
||||||
|
{0x1.ccec8c4ec5ef1p-1, 0x1.ccec8c4ec5ef3p-1},
|
||||||
|
{0x1.c8b73b9ac9a1dp-1, 0x1.c8b73b9ac9a1fp-1},
|
||||||
|
{0x1.5b5c75846aaf9p-3, 0x1.5b5c75846aafbp-3},
|
||||||
|
{0x1.40eb7cceb0482p-3, 0x1.40eb7cceb0484p-3},
|
||||||
|
{0x1.eb3e2ce365fd5p-4, 0x1.eb3e2ce365fd7p-4},
|
||||||
|
{0x1.eb3e2ce365fd5p-4, 0x1.eb3e2ce365fd7p-4},
|
||||||
|
{0x1.09dbd573f51e4p-4, 0x1.09dbd573f51e6p-4},
|
||||||
|
{-0x1.586e000000001p-305, 0x1.586e000000001p-305},
|
||||||
|
{0x1.dcce118e2abfap-1, 0x1.dcce118e2abfcp-1},
|
||||||
|
{0x1.5b5c75846aaf9p-3, 0x1.5b5c75846aafbp-3}}},
|
||||||
|
{{{0x1.fffffffffffffp-1, 0x1.0000000000001p+0},
|
||||||
|
{0x1.eada37c43dbcdp-1, 0x1.eada37c43dbd0p-1},
|
||||||
|
{0x1.d8eb0b5ab5aebp-1, 0x1.d8eb0b5ab5aeep-1},
|
||||||
|
{0x1.ccf1f94448ecfp-1, 0x1.ccf1f94448ed1p-1},
|
||||||
|
{0x1.c8badb25ce1b3p-1, 0x1.c8badb25ce1b6p-1},
|
||||||
|
{0x1.6c11624df5d8dp-3, 0x1.6c11624df5d96p-3},
|
||||||
|
{0x1.38a7a52a52899p-3, 0x1.38a7a52a5289dp-3},
|
||||||
|
{0x1.053de806af3a2p-3, 0x1.053de806af3a6p-3},
|
||||||
|
{0x1.fb41824cb7384p-4, 0x1.fb41824cb739ap-4},
|
||||||
|
{0x1.0303034e7937cp-4, 0x1.0303034e79384p-4},
|
||||||
|
{0x1.58908a0766c2ap-9, 0x1.58908a0767016p-9},
|
||||||
|
{0x1.dcd0247fc702ap-1, 0x1.dcd0247fc702dp-1},
|
||||||
|
{0x1.5b75854f419a5p-3, 0x1.5b75854f419aap-3}}},
|
||||||
|
{{{0x1.fffffffffffffp-1, 0x1.0000000000001p+0},
|
||||||
|
{0x1.ead630c226440p-1, 0x1.ead630c226443p-1},
|
||||||
|
{0x1.d8e399ff30e90p-1, 0x1.d8e399ff30e93p-1},
|
||||||
|
{0x1.cce84031702f6p-1, 0x1.cce84031702f8p-1},
|
||||||
|
{0x1.c8b05490c3638p-1, 0x1.c8b05490c363bp-1},
|
||||||
|
{0x1.6c56b7b4bec58p-3, 0x1.6c56b7b4bec60p-3},
|
||||||
|
{0x1.38e3300678b71p-3, 0x1.38e3300678b76p-3},
|
||||||
|
{0x1.056fa85832a89p-3, 0x1.056fa85832a8dp-3},
|
||||||
|
{0x1.fba21c895611fp-4, 0x1.fba21c8956135p-4},
|
||||||
|
{0x1.033456e6d857dp-4, 0x1.033456e6d8585p-4},
|
||||||
|
{0x1.58d2288b53912p-9, 0x1.58d2288b53cfep-9},
|
||||||
|
{0x1.dcc97107ca959p-1, 0x1.dcc97107ca95cp-1},
|
||||||
|
{0x1.5bb7b0fadf43fp-3, 0x1.5bb7b0fadf445p-3}}},
|
||||||
|
{{{0x1.fffffffffffffp-1, 0x1.0000000000001p+0},
|
||||||
|
{0x1.ead8344139715p-1, 0x1.ead8344139717p-1},
|
||||||
|
{0x1.d8e752a94ecadp-1, 0x1.d8e752a94ecafp-1},
|
||||||
|
{0x1.cced1cb61a4edp-1, 0x1.cced1cb61a4f0p-1},
|
||||||
|
{0x1.c8b597d621e34p-1, 0x1.c8b597d621e37p-1},
|
||||||
|
{0x1.6c340d23482b2p-3, 0x1.6c340d23482bap-3},
|
||||||
|
{0x1.38c56ab589a8cp-3, 0x1.38c56ab589a90p-3},
|
||||||
|
{0x1.0556c847cb264p-3, 0x1.0556c847cb268p-3},
|
||||||
|
{0x1.fb71cf9a50858p-4, 0x1.fb71cf9a5086ep-4},
|
||||||
|
{0x1.031bad32ce78bp-4, 0x1.031bad32ce792p-4},
|
||||||
|
{0x1.58b159698d54ep-9, 0x1.58b159698d939p-9},
|
||||||
|
{0x1.dccccac08134cp-1, 0x1.dccccac08134fp-1},
|
||||||
|
{0x1.5b969b4572366p-3, 0x1.5b969b457236cp-3}}},
|
||||||
|
},
|
||||||
|
}};
|
||||||
|
} // namespace thermal_oracle
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "fesa/app/fesa_application.h"
|
||||||
|
#include "thermal_reference_comparison.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::string Bytes(const std::filesystem::path& path) {
|
||||||
|
std::ifstream in{path, std::ios::binary};
|
||||||
|
return {std::istreambuf_iterator<char>{in}, std::istreambuf_iterator<char>{}};
|
||||||
|
}
|
||||||
|
TEST(ThermalReference, OriginalAbaqusNt11RemainsAnExplicitFailingDiagnostic) {
|
||||||
|
const auto folder =
|
||||||
|
std::filesystem::path{FESA_TEST_SOURCE_DIR} / "reference/shell-thermal";
|
||||||
|
const auto input = folder / "shell-thermal.inp";
|
||||||
|
const auto csv = folder / "shell-thermal NodalTemperature.csv";
|
||||||
|
const auto before_input = Bytes(input), before_csv = Bytes(csv);
|
||||||
|
ASSERT_FALSE(before_input.empty());
|
||||||
|
ASSERT_FALSE(before_csv.empty());
|
||||||
|
const int exit = fesa::FesaApplication{}.Run({input.string()});
|
||||||
|
EXPECT_EQ(Bytes(input), before_input);
|
||||||
|
EXPECT_EQ(Bytes(csv), before_csv);
|
||||||
|
ASSERT_EQ(exit, 0);
|
||||||
|
const auto result = fesa::test::CompareThermalReferenceFiles(
|
||||||
|
input, csv, folder / "shell-thermal_results.h5");
|
||||||
|
ASSERT_TRUE(result.HasValue())
|
||||||
|
<< (result.HasValue() ? "" : result.GetStatus().Diagnostics()[0].message);
|
||||||
|
const auto& r = result.Value();
|
||||||
|
ASSERT_EQ(r.rows.size(), 729U);
|
||||||
|
const auto output = std::filesystem::path{FESA_TEST_BINARY_DIR} /
|
||||||
|
"thermal-reference-metrics.csv";
|
||||||
|
std::ofstream rows{output};
|
||||||
|
rows
|
||||||
|
<< "instance,label,dof,actual,reference,absolute_error,threshold,passed\n"
|
||||||
|
<< std::setprecision(17);
|
||||||
|
std::size_t failed = 0;
|
||||||
|
for (const auto& row : r.rows) {
|
||||||
|
failed += !row.tolerance.passed;
|
||||||
|
rows << row.instance << ',' << row.label << ',' << row.source_dof << ','
|
||||||
|
<< row.actual << ',' << row.reference << ','
|
||||||
|
<< row.tolerance.absolute_error << ',' << row.tolerance.threshold
|
||||||
|
<< ',' << row.tolerance.passed << '\n';
|
||||||
|
}
|
||||||
|
ASSERT_TRUE(rows.good());
|
||||||
|
std::cout << std::setprecision(17)
|
||||||
|
<< "THERMAL_REFERENCE rows=" << r.rows.size()
|
||||||
|
<< " S=" << r.family.reference_scale
|
||||||
|
<< " B=" << r.family.near_zero_band
|
||||||
|
<< " RMS/S=" << r.family.relative_rms
|
||||||
|
<< " max_abs=" << r.family.maximum_absolute_error
|
||||||
|
<< " failed_rows=" << failed << " numerical_passed=" << r.passed
|
||||||
|
<< " classification=diagnostic blocking=" << r.family.blocking
|
||||||
|
<< '\n';
|
||||||
|
EXPECT_EQ(r.family.reference_scale, 1000);
|
||||||
|
EXPECT_EQ(r.family.near_zero_band, 10);
|
||||||
|
EXPECT_FALSE(r.family.blocking);
|
||||||
|
EXPECT_FALSE(r.passed);
|
||||||
|
EXPECT_FALSE(r.family.rms_passed);
|
||||||
|
EXPECT_EQ(failed, 87U);
|
||||||
|
EXPECT_GT(r.family.relative_rms,
|
||||||
|
fesa::test::ReferenceTolerancePolicy::kRelativeRmsTolerance);
|
||||||
|
EXPECT_GT(r.family.maximum_absolute_error, r.family.near_zero_band);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
@@ -0,0 +1,417 @@
|
|||||||
|
#include "thermal_reference_comparison.h"
|
||||||
|
|
||||||
|
#include <hdf5.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <charconv>
|
||||||
|
#include <cmath>
|
||||||
|
#include <fstream>
|
||||||
|
#include <map>
|
||||||
|
#include <regex>
|
||||||
|
#include <set>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "fesa/analysis/prepared_analysis.h"
|
||||||
|
|
||||||
|
namespace fesa::test {
|
||||||
|
namespace {
|
||||||
|
Status Failure(const std::string& message) {
|
||||||
|
return Status::Failure(FailureCategory::kModel,
|
||||||
|
{{Severity::kError,
|
||||||
|
"invalid-thermal-reference",
|
||||||
|
{},
|
||||||
|
"HEAT TRANSFER",
|
||||||
|
"shell-thermal/Step-1/t=37/NT11",
|
||||||
|
message}});
|
||||||
|
}
|
||||||
|
void Require(bool condition, const std::string& message) {
|
||||||
|
if (!condition) throw std::runtime_error{message};
|
||||||
|
}
|
||||||
|
class H {
|
||||||
|
public:
|
||||||
|
H(hid_t id, herr_t (*close)(hid_t)) : id_(id), close_(close) {
|
||||||
|
Require(id >= 0, "Missing or invalid HDF5 object.");
|
||||||
|
}
|
||||||
|
~H() { close_(id_); }
|
||||||
|
H(const H&) = delete;
|
||||||
|
H& operator=(const H&) = delete;
|
||||||
|
hid_t Id() const { return id_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
hid_t id_;
|
||||||
|
herr_t (*close_)(hid_t);
|
||||||
|
};
|
||||||
|
std::vector<hsize_t> Shape(hid_t dataset) {
|
||||||
|
H space{H5Dget_space(dataset), H5Sclose};
|
||||||
|
const int n = H5Sget_simple_extent_ndims(space.Id());
|
||||||
|
Require(n >= 0, "Invalid dataset dataspace.");
|
||||||
|
std::vector<hsize_t> dims(static_cast<std::size_t>(n));
|
||||||
|
Require(H5Sget_simple_extent_dims(space.Id(), dims.data(), nullptr) >= 0,
|
||||||
|
"Invalid dimensions.");
|
||||||
|
return dims;
|
||||||
|
}
|
||||||
|
std::string Attr(hid_t object, const char* name) {
|
||||||
|
H a{H5Aopen(object, name, H5P_DEFAULT), H5Aclose};
|
||||||
|
H t{H5Aget_type(a.Id()), H5Tclose};
|
||||||
|
H s{H5Aget_space(a.Id()), H5Sclose};
|
||||||
|
Require(H5Sget_simple_extent_type(s.Id()) == H5S_SCALAR &&
|
||||||
|
H5Tget_class(t.Id()) == H5T_STRING &&
|
||||||
|
H5Tget_cset(t.Id()) == H5T_CSET_UTF8,
|
||||||
|
"Invalid UTF8 scalar attribute.");
|
||||||
|
if (H5Tis_variable_str(t.Id()) > 0) {
|
||||||
|
char* p = nullptr;
|
||||||
|
Require(H5Aread(a.Id(), t.Id(), &p) >= 0 && p != nullptr,
|
||||||
|
"Cannot read string.");
|
||||||
|
std::string result{p};
|
||||||
|
H5free_memory(p);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
std::vector<char> p(H5Tget_size(t.Id()) + 1, 0);
|
||||||
|
Require(H5Aread(a.Id(), t.Id(), p.data()) >= 0, "Cannot read fixed string.");
|
||||||
|
return std::string{p.data()};
|
||||||
|
}
|
||||||
|
template <class T>
|
||||||
|
T Scalar(hid_t object, const char* name, hid_t disk, hid_t memory) {
|
||||||
|
H a{H5Aopen(object, name, H5P_DEFAULT), H5Aclose};
|
||||||
|
H t{H5Aget_type(a.Id()), H5Tclose};
|
||||||
|
H s{H5Aget_space(a.Id()), H5Sclose};
|
||||||
|
Require(H5Tequal(t.Id(), disk) > 0 &&
|
||||||
|
H5Sget_simple_extent_type(s.Id()) == H5S_SCALAR,
|
||||||
|
"Invalid scalar type/shape.");
|
||||||
|
T value{};
|
||||||
|
Require(H5Aread(a.Id(), memory, &value) >= 0, "Cannot read scalar.");
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
template <class T>
|
||||||
|
std::vector<T> Primitive(hid_t file, const char* path,
|
||||||
|
const std::vector<hsize_t>& dims, hid_t disk,
|
||||||
|
hid_t memory) {
|
||||||
|
H d{H5Dopen2(file, path, H5P_DEFAULT), H5Dclose};
|
||||||
|
H t{H5Dget_type(d.Id()), H5Tclose};
|
||||||
|
Require(Shape(d.Id()) == dims && H5Tequal(t.Id(), disk) > 0,
|
||||||
|
"Wrong dataset primitive/extent.");
|
||||||
|
std::size_t n = 1;
|
||||||
|
for (auto extent : dims) n *= static_cast<std::size_t>(extent);
|
||||||
|
std::vector<T> values(n);
|
||||||
|
Require(H5Dread(d.Id(), memory, H5S_ALL, H5S_ALL, H5P_DEFAULT,
|
||||||
|
values.data()) >= 0,
|
||||||
|
"Cannot read primitive.");
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
void Field(hid_t compound, const char* name, hid_t expected) {
|
||||||
|
Require(H5Tget_class(compound) == H5T_COMPOUND, "Expected compound.");
|
||||||
|
const int index = H5Tget_member_index(compound, name);
|
||||||
|
Require(index >= 0, "Missing compound field.");
|
||||||
|
H type{H5Tget_member_type(compound, static_cast<unsigned>(index)), H5Tclose};
|
||||||
|
Require(H5Tequal(type.Id(), expected) > 0,
|
||||||
|
"Invalid compound field primitive.");
|
||||||
|
}
|
||||||
|
std::uint64_t Label(const std::string& text) {
|
||||||
|
std::uint64_t value = 0;
|
||||||
|
const auto parsed =
|
||||||
|
std::from_chars(text.data(), text.data() + text.size(), value);
|
||||||
|
Require(parsed.ec == std::errc{} && parsed.ptr == text.data() + text.size() &&
|
||||||
|
value > 0,
|
||||||
|
"Invalid node label.");
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
std::string Trim(const std::string& text) {
|
||||||
|
const auto begin = text.find_first_not_of(" \t\r\n");
|
||||||
|
if (begin == std::string::npos) return {};
|
||||||
|
return text.substr(begin, text.find_last_not_of(" \t\r\n") - begin + 1);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Result<ThermalReferenceReport> CompareThermalSnapshots(
|
||||||
|
const ThermalReferenceSnapshot& actual,
|
||||||
|
const ThermalReferenceSnapshot& reference,
|
||||||
|
const std::vector<std::uint64_t>& expected_labels,
|
||||||
|
const std::string& instance) {
|
||||||
|
try {
|
||||||
|
const std::set<std::uint64_t> labels(expected_labels.begin(),
|
||||||
|
expected_labels.end());
|
||||||
|
Require(!labels.empty() && labels.size() == expected_labels.size() &&
|
||||||
|
!instance.empty(),
|
||||||
|
"Invalid input node inventory.");
|
||||||
|
auto mapped = [&](const ThermalReferenceSnapshot& s) {
|
||||||
|
Require(s.step == "Step-1" && s.time == 37 && s.increment == 37 &&
|
||||||
|
s.source_dof == 11,
|
||||||
|
"Wrong source step/time/increment/station.");
|
||||||
|
std::map<std::uint64_t, double> result;
|
||||||
|
for (const auto& row : s.rows) {
|
||||||
|
Require(row.instance == instance && labels.count(row.label) == 1 &&
|
||||||
|
std::isfinite(row.value),
|
||||||
|
"Wrong instance/extra label/nonfinite value.");
|
||||||
|
Require(result.emplace(row.label, row.value).second,
|
||||||
|
"Duplicate node identity.");
|
||||||
|
}
|
||||||
|
Require(result.size() == labels.size(), "Missing node identity.");
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
const auto a = mapped(actual), r = mapped(reference);
|
||||||
|
ReferenceToleranceFamily family{
|
||||||
|
"shell-thermal/Step-1/time=37/NT11/temperature/user-consistent",
|
||||||
|
{"NT11"},
|
||||||
|
false,
|
||||||
|
{}};
|
||||||
|
for (auto label : labels) family.rows.push_back({a.at(label), r.at(label)});
|
||||||
|
const auto evaluation = ReferenceTolerancePolicy::Evaluate({family});
|
||||||
|
Require(evaluation.valid && evaluation.families.size() == 1,
|
||||||
|
"Invalid tolerance evaluation.");
|
||||||
|
ThermalReferenceReport report{
|
||||||
|
evaluation.families[0].passed, {}, evaluation.families[0]};
|
||||||
|
std::size_t index = 0;
|
||||||
|
for (auto label : labels)
|
||||||
|
report.rows.push_back({instance, label, 11, a.at(label), r.at(label),
|
||||||
|
report.family.rows[index++]});
|
||||||
|
return Result<ThermalReferenceReport>::Success(std::move(report));
|
||||||
|
} catch (const std::exception& error) {
|
||||||
|
return Result<ThermalReferenceReport>::Failure(Failure(error.what()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<ThermalReferenceSnapshot> ReadThermalReferenceCsv(
|
||||||
|
const std::filesystem::path& path, const std::string& sole_instance) {
|
||||||
|
try {
|
||||||
|
Require(!sole_instance.empty(), "CSV needs a proven sole input instance.");
|
||||||
|
std::ifstream input{path};
|
||||||
|
Require(input.good(), "Cannot read original CSV.");
|
||||||
|
std::string line;
|
||||||
|
Require(static_cast<bool>(std::getline(input, line)),
|
||||||
|
"Missing CSV header.");
|
||||||
|
auto split = [](const std::string& row) {
|
||||||
|
std::vector<std::string> values;
|
||||||
|
std::istringstream in{row};
|
||||||
|
std::string value;
|
||||||
|
while (std::getline(in, value, ',')) values.push_back(Trim(value));
|
||||||
|
return values;
|
||||||
|
};
|
||||||
|
Require(split(line) ==
|
||||||
|
std::vector<std::string>{"Step", "Frame", "Node Label", "NT11"},
|
||||||
|
"Wrong CSV columns/station.");
|
||||||
|
ThermalReferenceSnapshot result{"Step-1", 37, 37, 11, {}};
|
||||||
|
const std::regex frame{R"(^Increment\s+37:\s+Step Time\s*=\s*37\.00$)"};
|
||||||
|
while (std::getline(input, line)) {
|
||||||
|
const auto row = split(line);
|
||||||
|
Require(row.size() == 4 && row[0] == "Step-1" &&
|
||||||
|
std::regex_match(row[1], frame),
|
||||||
|
"Wrong CSV source frame.");
|
||||||
|
std::size_t consumed = 0;
|
||||||
|
const double value = std::stod(row[3], &consumed);
|
||||||
|
Require(consumed == row[3].size() && std::isfinite(value),
|
||||||
|
"Invalid CSV value.");
|
||||||
|
result.rows.push_back({sole_instance, Label(row[2]), value});
|
||||||
|
}
|
||||||
|
Require(input.eof(), "CSV read failed.");
|
||||||
|
return Result<ThermalReferenceSnapshot>::Success(std::move(result));
|
||||||
|
} catch (const std::exception& error) {
|
||||||
|
return Result<ThermalReferenceSnapshot>::Failure(Failure(error.what()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<ThermalReferenceSnapshot> ReadThermalReferenceHdf5(
|
||||||
|
const std::filesystem::path& path) {
|
||||||
|
try {
|
||||||
|
H file{H5Fopen(path.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT),
|
||||||
|
H5Fclose};
|
||||||
|
H meta{H5Gopen2(file.Id(), "/metadata", H5P_DEFAULT), H5Gclose};
|
||||||
|
Require(
|
||||||
|
Scalar<std::uint64_t>(meta.Id(), "schema_version", H5T_STD_U64LE,
|
||||||
|
H5T_NATIVE_UINT64) == 1 &&
|
||||||
|
Attr(meta.Id(), "feature_id") == "shell-transient-heat-transfer" &&
|
||||||
|
Attr(meta.Id(), "analysis_procedure") ==
|
||||||
|
"uncoupled-transient-heat-transfer" &&
|
||||||
|
Attr(meta.Id(), "source_step_name") == "Step-1" &&
|
||||||
|
Attr(meta.Id(), "unit_system_label") ==
|
||||||
|
"user-consistent-unspecified",
|
||||||
|
"Wrong thermal metadata.");
|
||||||
|
Require(Primitive<std::uint64_t>(file.Id(), "/model/temperature_dofs", {5},
|
||||||
|
H5T_STD_U64LE, H5T_NATIVE_UINT64) ==
|
||||||
|
std::vector<std::uint64_t>{11, 12, 13, 14, 15},
|
||||||
|
"Wrong station DOFs.");
|
||||||
|
Require(Primitive<double>(file.Id(), "/model/temperature_zeta", {5},
|
||||||
|
H5T_IEEE_F64LE, H5T_NATIVE_DOUBLE) ==
|
||||||
|
std::vector<double>{-1, -.5, 0, .5, 1},
|
||||||
|
"Wrong station coordinates.");
|
||||||
|
H nodes{H5Dopen2(file.Id(), "/model/nodes", H5P_DEFAULT), H5Dclose};
|
||||||
|
H nt{H5Dget_type(nodes.Id()), H5Tclose};
|
||||||
|
const auto shape = Shape(nodes.Id());
|
||||||
|
Require(shape.size() == 1 && shape[0] > 0, "Invalid nodes shape.");
|
||||||
|
Field(nt.Id(), "internal_node_id", H5T_STD_U64LE);
|
||||||
|
struct NodeRow {
|
||||||
|
std::uint64_t id;
|
||||||
|
char* instance;
|
||||||
|
char* label;
|
||||||
|
};
|
||||||
|
H string{H5Tcopy(H5T_C_S1), H5Tclose};
|
||||||
|
H5Tset_size(string.Id(), H5T_VARIABLE);
|
||||||
|
H5Tset_cset(string.Id(), H5T_CSET_UTF8);
|
||||||
|
for (const auto* name : {"instance_name", "source_label"}) {
|
||||||
|
const int index = H5Tget_member_index(nt.Id(), name);
|
||||||
|
Require(index >= 0, "Missing identity field.");
|
||||||
|
H type{H5Tget_member_type(nt.Id(), static_cast<unsigned>(index)),
|
||||||
|
H5Tclose};
|
||||||
|
Require(H5Tget_class(type.Id()) == H5T_STRING &&
|
||||||
|
H5Tget_cset(type.Id()) == H5T_CSET_UTF8,
|
||||||
|
"Wrong identity type.");
|
||||||
|
}
|
||||||
|
H memory{H5Tcreate(H5T_COMPOUND, sizeof(NodeRow)), H5Tclose};
|
||||||
|
H5Tinsert(memory.Id(), "internal_node_id", HOFFSET(NodeRow, id),
|
||||||
|
H5T_NATIVE_UINT64);
|
||||||
|
H5Tinsert(memory.Id(), "instance_name", HOFFSET(NodeRow, instance),
|
||||||
|
string.Id());
|
||||||
|
H5Tinsert(memory.Id(), "source_label", HOFFSET(NodeRow, label),
|
||||||
|
string.Id());
|
||||||
|
std::vector<NodeRow> raw(static_cast<std::size_t>(shape[0]));
|
||||||
|
Require(H5Dread(nodes.Id(), memory.Id(), H5S_ALL, H5S_ALL, H5P_DEFAULT,
|
||||||
|
raw.data()) >= 0,
|
||||||
|
"Cannot read node identities.");
|
||||||
|
std::map<std::uint64_t, ThermalReferenceRow> identities;
|
||||||
|
bool valid = true;
|
||||||
|
for (const auto& row : raw) {
|
||||||
|
if (!row.instance || !row.label)
|
||||||
|
valid = false;
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
valid = identities
|
||||||
|
.emplace(row.id, ThermalReferenceRow{row.instance,
|
||||||
|
Label(row.label), 0})
|
||||||
|
.second &&
|
||||||
|
valid;
|
||||||
|
} catch (...) {
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
H ns{H5Dget_space(nodes.Id()), H5Sclose};
|
||||||
|
Require(
|
||||||
|
H5Treclaim(memory.Id(), ns.Id(), H5P_DEFAULT, raw.data()) >= 0 && valid,
|
||||||
|
"Invalid node identity inventory.");
|
||||||
|
const auto active =
|
||||||
|
Primitive<std::uint64_t>(file.Id(), "/model/thermal_node_ids", shape,
|
||||||
|
H5T_STD_U64LE, H5T_NATIVE_UINT64);
|
||||||
|
Require(std::set<std::uint64_t>(active.begin(), active.end()).size() ==
|
||||||
|
active.size(),
|
||||||
|
"Duplicate active IDs.");
|
||||||
|
struct Frame {
|
||||||
|
std::uint64_t frame, increment;
|
||||||
|
double time, delta;
|
||||||
|
std::uint8_t initial;
|
||||||
|
};
|
||||||
|
H fd{H5Dopen2(file.Id(), "/steps/Step-1/accepted_frames", H5P_DEFAULT),
|
||||||
|
H5Dclose};
|
||||||
|
H ft{H5Dget_type(fd.Id()), H5Tclose};
|
||||||
|
H fm{H5Tcreate(H5T_COMPOUND, sizeof(Frame)), H5Tclose};
|
||||||
|
for (const auto* name : {"frame_index", "increment_index"})
|
||||||
|
Field(ft.Id(), name, H5T_STD_U64LE);
|
||||||
|
for (const auto* name : {"time", "time_increment"})
|
||||||
|
Field(ft.Id(), name, H5T_IEEE_F64LE);
|
||||||
|
Field(ft.Id(), "is_initial", H5T_STD_U8LE);
|
||||||
|
H5Tinsert(fm.Id(), "frame_index", HOFFSET(Frame, frame), H5T_NATIVE_UINT64);
|
||||||
|
H5Tinsert(fm.Id(), "increment_index", HOFFSET(Frame, increment),
|
||||||
|
H5T_NATIVE_UINT64);
|
||||||
|
H5Tinsert(fm.Id(), "time", HOFFSET(Frame, time), H5T_NATIVE_DOUBLE);
|
||||||
|
H5Tinsert(fm.Id(), "time_increment", HOFFSET(Frame, delta),
|
||||||
|
H5T_NATIVE_DOUBLE);
|
||||||
|
H5Tinsert(fm.Id(), "is_initial", HOFFSET(Frame, initial), H5T_NATIVE_UINT8);
|
||||||
|
const auto fs = Shape(fd.Id());
|
||||||
|
Require(fs == std::vector<hsize_t>{101}, "Missing/extra reference frames.");
|
||||||
|
std::vector<Frame> frames(static_cast<std::size_t>(fs[0]));
|
||||||
|
Require(H5Dread(fd.Id(), fm.Id(), H5S_ALL, H5S_ALL, H5P_DEFAULT,
|
||||||
|
frames.data()) >= 0,
|
||||||
|
"Cannot read frames.");
|
||||||
|
std::size_t matches = 0;
|
||||||
|
for (std::size_t i = 0; i < frames.size(); ++i) {
|
||||||
|
const auto& f = frames[i];
|
||||||
|
Require(f.frame == i && f.increment == i && f.initial == (i == 0) &&
|
||||||
|
std::isfinite(f.time) && std::isfinite(f.delta),
|
||||||
|
"Invalid/duplicate frame inventory.");
|
||||||
|
Require(f.time == static_cast<double>(i) && f.delta == (i == 0 ? 0. : 1.),
|
||||||
|
"Wrong frame schedule.");
|
||||||
|
if (f.time == 37) {
|
||||||
|
++matches;
|
||||||
|
Require(f.increment == 37, "Wrong reference increment.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Require(matches == 1 && frames[37].time == 37,
|
||||||
|
"No unique exact t=37 frame.");
|
||||||
|
H group{H5Gopen2(file.Id(), "/steps/Step-1/frames/37", H5P_DEFAULT),
|
||||||
|
H5Gclose};
|
||||||
|
Require(Scalar<double>(group.Id(), "time", H5T_IEEE_F64LE,
|
||||||
|
H5T_NATIVE_DOUBLE) == 37 &&
|
||||||
|
Scalar<std::uint64_t>(group.Id(), "frame_index", H5T_STD_U64LE,
|
||||||
|
H5T_NATIVE_UINT64) == 37 &&
|
||||||
|
Scalar<std::uint64_t>(group.Id(), "increment_index",
|
||||||
|
H5T_STD_U64LE, H5T_NATIVE_UINT64) == 37 &&
|
||||||
|
Scalar<std::uint8_t>(group.Id(), "is_initial", H5T_STD_U8LE,
|
||||||
|
H5T_NATIVE_UINT8) == 0,
|
||||||
|
"Wrong selected frame attributes.");
|
||||||
|
const auto values = Primitive<double>(
|
||||||
|
file.Id(), "/steps/Step-1/frames/37/nodal/temperature", {shape[0], 5},
|
||||||
|
H5T_IEEE_F64LE, H5T_NATIVE_DOUBLE);
|
||||||
|
ThermalReferenceSnapshot result{"Step-1", 37, 37, 11, {}};
|
||||||
|
result.source_input_identity = Attr(meta.Id(), "source_input_identity");
|
||||||
|
H temperature{
|
||||||
|
H5Dopen2(file.Id(), "/steps/Step-1/frames/37/nodal/temperature",
|
||||||
|
H5P_DEFAULT),
|
||||||
|
H5Dclose};
|
||||||
|
Require(
|
||||||
|
Attr(temperature.Id(), "component_order") ==
|
||||||
|
"NT11,NT12,NT13,NT14,NT15" &&
|
||||||
|
Attr(temperature.Id(), "units_label") == "temperature" &&
|
||||||
|
Attr(temperature.Id(), "coordinate_system") == "scalar" &&
|
||||||
|
Attr(temperature.Id(), "location") == "nodal-thickness-stations",
|
||||||
|
"Wrong temperature component/units/location metadata.");
|
||||||
|
for (double value : values)
|
||||||
|
Require(std::isfinite(value), "Nonfinite temperature station.");
|
||||||
|
for (std::size_t i = 0; i < active.size(); ++i) {
|
||||||
|
Require(identities.count(active[i]) == 1, "Unknown active node ID.");
|
||||||
|
auto row = identities.at(active[i]);
|
||||||
|
row.value = values[5 * i];
|
||||||
|
result.rows.push_back(std::move(row));
|
||||||
|
}
|
||||||
|
return Result<ThermalReferenceSnapshot>::Success(std::move(result));
|
||||||
|
} catch (const std::exception& error) {
|
||||||
|
return Result<ThermalReferenceSnapshot>::Failure(Failure(error.what()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<ThermalReferenceReport> CompareThermalReferenceFiles(
|
||||||
|
const std::filesystem::path& input, const std::filesystem::path& csv,
|
||||||
|
const std::filesystem::path& hdf5) {
|
||||||
|
auto prepared = PrepareAnalysisInput(input);
|
||||||
|
if (!prepared.HasValue())
|
||||||
|
return Result<ThermalReferenceReport>::Failure(prepared.GetStatus());
|
||||||
|
const auto& domain = prepared.Value().domain;
|
||||||
|
if (!domain.ThermalStep() || domain.ThermalStep()->name != "Step-1" ||
|
||||||
|
domain.Nodes().size() != 729 || domain.ThermalElements().Size() != 676)
|
||||||
|
return Result<ThermalReferenceReport>::Failure(
|
||||||
|
Failure("Unexpected reference input inventory."));
|
||||||
|
std::vector<std::uint64_t> labels;
|
||||||
|
for (const auto& node : domain.Nodes()) {
|
||||||
|
if (node.source_id.instance_name != "Part-1-1")
|
||||||
|
return Result<ThermalReferenceReport>::Failure(
|
||||||
|
Failure("CSV requires the sole Part-1-1 input instance."));
|
||||||
|
labels.push_back(static_cast<std::uint64_t>(node.source_id.source_label));
|
||||||
|
}
|
||||||
|
const auto actual = ReadThermalReferenceHdf5(hdf5),
|
||||||
|
reference = ReadThermalReferenceCsv(csv, "Part-1-1");
|
||||||
|
if (!actual.HasValue())
|
||||||
|
return Result<ThermalReferenceReport>::Failure(actual.GetStatus());
|
||||||
|
if (!reference.HasValue())
|
||||||
|
return Result<ThermalReferenceReport>::Failure(reference.GetStatus());
|
||||||
|
const std::string expected_identity =
|
||||||
|
"path=" +
|
||||||
|
std::filesystem::absolute(domain.SourcePath())
|
||||||
|
.lexically_normal()
|
||||||
|
.generic_u8string() +
|
||||||
|
";content_identity=" + domain.SourceContentIdentity();
|
||||||
|
if (actual.Value().source_input_identity != expected_identity)
|
||||||
|
return Result<ThermalReferenceReport>::Failure(
|
||||||
|
Failure("Wrong source input identity."));
|
||||||
|
return CompareThermalSnapshots(actual.Value(), reference.Value(), labels,
|
||||||
|
"Part-1-1");
|
||||||
|
}
|
||||||
|
} // namespace fesa::test
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#ifndef FESA_TESTS_REFERENCE_THERMAL_REFERENCE_COMPARISON_H_
|
||||||
|
#define FESA_TESTS_REFERENCE_THERMAL_REFERENCE_COMPARISON_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fesa/core/status.h"
|
||||||
|
#include "reference_tolerance_policy.h"
|
||||||
|
|
||||||
|
namespace fesa::test {
|
||||||
|
struct ThermalReferenceRow {
|
||||||
|
std::string instance;
|
||||||
|
std::uint64_t label;
|
||||||
|
double value;
|
||||||
|
};
|
||||||
|
struct ThermalReferenceSnapshot {
|
||||||
|
std::string step;
|
||||||
|
double time = 0;
|
||||||
|
std::uint64_t increment = 0;
|
||||||
|
int source_dof = 0;
|
||||||
|
std::vector<ThermalReferenceRow> rows;
|
||||||
|
std::string source_input_identity;
|
||||||
|
};
|
||||||
|
struct ThermalReferenceRowDecision {
|
||||||
|
std::string instance;
|
||||||
|
std::uint64_t label;
|
||||||
|
int source_dof;
|
||||||
|
double actual;
|
||||||
|
double reference;
|
||||||
|
ReferenceToleranceRowDecision tolerance;
|
||||||
|
};
|
||||||
|
struct ThermalReferenceReport {
|
||||||
|
// Numerical family verdict, retained even when the original artifact is
|
||||||
|
// classified as nonblocking diagnostic by the approved option A decision.
|
||||||
|
bool passed = false;
|
||||||
|
std::vector<ThermalReferenceRowDecision> rows;
|
||||||
|
ReferenceToleranceFamilyDecision family;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Identity checks precede the unchanged common tolerance policy. Expected
|
||||||
|
/// labels come from the actual input Domain, never CSV row order.
|
||||||
|
Result<ThermalReferenceReport> CompareThermalSnapshots(
|
||||||
|
const ThermalReferenceSnapshot& actual,
|
||||||
|
const ThermalReferenceSnapshot& reference,
|
||||||
|
const std::vector<std::uint64_t>& expected_labels,
|
||||||
|
const std::string& instance);
|
||||||
|
Result<ThermalReferenceSnapshot> ReadThermalReferenceCsv(
|
||||||
|
const std::filesystem::path& path, const std::string& sole_instance);
|
||||||
|
Result<ThermalReferenceSnapshot> ReadThermalReferenceHdf5(
|
||||||
|
const std::filesystem::path& path);
|
||||||
|
Result<ThermalReferenceReport> CompareThermalReferenceFiles(
|
||||||
|
const std::filesystem::path& input, const std::filesystem::path& csv,
|
||||||
|
const std::filesystem::path& hdf5);
|
||||||
|
} // namespace fesa::test
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,330 @@
|
|||||||
|
#include "thermal_reference_comparison.h"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <hdf5.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
|
#include <cmath>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <limits>
|
||||||
|
#include <random>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
using fesa::test::ThermalReferenceSnapshot;
|
||||||
|
class TemporaryFolder {
|
||||||
|
public:
|
||||||
|
TemporaryFolder() {
|
||||||
|
static std::atomic<unsigned> next{0};
|
||||||
|
do {
|
||||||
|
path =
|
||||||
|
std::filesystem::temp_directory_path() /
|
||||||
|
("fesa-thermal-reference-" + std::to_string(std::random_device{}()) +
|
||||||
|
"-" + std::to_string(next++));
|
||||||
|
} while (!std::filesystem::create_directory(path));
|
||||||
|
}
|
||||||
|
~TemporaryFolder() {
|
||||||
|
std::error_code error;
|
||||||
|
std::filesystem::remove_all(path, error);
|
||||||
|
}
|
||||||
|
std::filesystem::path path;
|
||||||
|
};
|
||||||
|
class Handle {
|
||||||
|
public:
|
||||||
|
Handle(hid_t id, herr_t (*closer)(hid_t)) : id(id), closer(closer) {
|
||||||
|
if (id < 0) throw std::runtime_error{"HDF fixture creation failed."};
|
||||||
|
}
|
||||||
|
~Handle() { closer(id); }
|
||||||
|
Handle(const Handle&) = delete;
|
||||||
|
Handle& operator=(const Handle&) = delete;
|
||||||
|
hid_t id;
|
||||||
|
|
||||||
|
private:
|
||||||
|
herr_t (*closer)(hid_t);
|
||||||
|
};
|
||||||
|
void StringAttribute(hid_t object, const char* name, const std::string& value) {
|
||||||
|
Handle type{H5Tcopy(H5T_C_S1), H5Tclose};
|
||||||
|
ASSERT_GE(H5Tset_size(type.id, H5T_VARIABLE), 0);
|
||||||
|
ASSERT_GE(H5Tset_cset(type.id, H5T_CSET_UTF8), 0);
|
||||||
|
Handle space{H5Screate(H5S_SCALAR), H5Sclose};
|
||||||
|
Handle attr{
|
||||||
|
H5Acreate2(object, name, type.id, space.id, H5P_DEFAULT, H5P_DEFAULT),
|
||||||
|
H5Aclose};
|
||||||
|
const char* text = value.c_str();
|
||||||
|
ASSERT_GE(H5Awrite(attr.id, type.id, &text), 0);
|
||||||
|
}
|
||||||
|
template <class T>
|
||||||
|
void ScalarAttribute(hid_t object, const char* name, hid_t type, T value) {
|
||||||
|
Handle space{H5Screate(H5S_SCALAR), H5Sclose};
|
||||||
|
Handle attr{
|
||||||
|
H5Acreate2(object, name, type, space.id, H5P_DEFAULT, H5P_DEFAULT),
|
||||||
|
H5Aclose};
|
||||||
|
ASSERT_GE(H5Awrite(attr.id, type, &value), 0);
|
||||||
|
}
|
||||||
|
void Dataset(hid_t file, const char* path, hid_t type,
|
||||||
|
const std::vector<hsize_t>& shape, const void* data) {
|
||||||
|
Handle space{
|
||||||
|
H5Screate_simple(static_cast<int>(shape.size()), shape.data(), nullptr),
|
||||||
|
H5Sclose};
|
||||||
|
Handle dataset{H5Dcreate2(file, path, type, space.id, H5P_DEFAULT,
|
||||||
|
H5P_DEFAULT, H5P_DEFAULT),
|
||||||
|
H5Dclose};
|
||||||
|
ASSERT_GE(H5Dwrite(dataset.id, type, H5S_ALL, H5S_ALL, H5P_DEFAULT, data), 0);
|
||||||
|
}
|
||||||
|
// Minimal independent schema fixture for the comparator (not a solver output
|
||||||
|
// or a replacement for the immutable Abaqus artifact).
|
||||||
|
void HdfFixture(const std::filesystem::path& path, int defect) {
|
||||||
|
Handle file{
|
||||||
|
H5Fcreate(path.string().c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT),
|
||||||
|
H5Fclose};
|
||||||
|
for (const auto* name : {"/metadata", "/model", "/steps", "/steps/Step-1",
|
||||||
|
"/steps/Step-1/frames", "/steps/Step-1/frames/37",
|
||||||
|
"/steps/Step-1/frames/37/nodal"}) {
|
||||||
|
Handle group{
|
||||||
|
H5Gcreate2(file.id, name, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT),
|
||||||
|
H5Gclose};
|
||||||
|
}
|
||||||
|
Handle metadata{H5Gopen2(file.id, "/metadata", H5P_DEFAULT), H5Gclose};
|
||||||
|
ScalarAttribute<std::uint64_t>(metadata.id, "schema_version", H5T_STD_U64LE,
|
||||||
|
1);
|
||||||
|
StringAttribute(metadata.id, "feature_id", "shell-transient-heat-transfer");
|
||||||
|
StringAttribute(metadata.id, "analysis_procedure",
|
||||||
|
"uncoupled-transient-heat-transfer");
|
||||||
|
StringAttribute(metadata.id, "source_step_name", "Step-1");
|
||||||
|
StringAttribute(metadata.id, "source_input_identity",
|
||||||
|
"synthetic-comparator-fixture");
|
||||||
|
StringAttribute(metadata.id, "unit_system_label",
|
||||||
|
"user-consistent-unspecified");
|
||||||
|
std::uint64_t dofs[5] = {11, 12, 13, 14, 15};
|
||||||
|
if (defect == 1) dofs[0] = 15;
|
||||||
|
Dataset(file.id, "/model/temperature_dofs", H5T_STD_U64LE, {5}, dofs);
|
||||||
|
double zeta[5] = {-1, -.5, 0, .5, 1};
|
||||||
|
if (defect == 2) zeta[0] = 0;
|
||||||
|
Dataset(file.id, "/model/temperature_zeta", H5T_IEEE_F64LE, {5}, zeta);
|
||||||
|
const std::uint64_t active[2] = {1, 0};
|
||||||
|
Dataset(file.id, "/model/thermal_node_ids", H5T_STD_U64LE, {2}, active);
|
||||||
|
struct Node {
|
||||||
|
std::uint64_t id;
|
||||||
|
const char* instance;
|
||||||
|
const char* label;
|
||||||
|
};
|
||||||
|
Node nodes[2] = {{0, "Part-1-1", "7"}, {1, "Part-1-1", "20"}};
|
||||||
|
if (defect == 9) nodes[1].id = 0;
|
||||||
|
if (defect == 10) nodes[1].instance = "Wrong";
|
||||||
|
if (defect == 11) nodes[1].label = "7";
|
||||||
|
Handle string{H5Tcopy(H5T_C_S1), H5Tclose};
|
||||||
|
H5Tset_size(string.id, H5T_VARIABLE);
|
||||||
|
H5Tset_cset(string.id, H5T_CSET_UTF8);
|
||||||
|
Handle nt{H5Tcreate(H5T_COMPOUND, sizeof(Node)), H5Tclose};
|
||||||
|
H5Tinsert(nt.id, "internal_node_id", HOFFSET(Node, id), H5T_STD_U64LE);
|
||||||
|
H5Tinsert(nt.id, "instance_name", HOFFSET(Node, instance), string.id);
|
||||||
|
if (defect != 14)
|
||||||
|
H5Tinsert(nt.id, "source_label", HOFFSET(Node, label), string.id);
|
||||||
|
Dataset(file.id, "/model/nodes", nt.id, {2}, nodes);
|
||||||
|
struct Frame {
|
||||||
|
std::uint64_t frame, increment;
|
||||||
|
double time, delta;
|
||||||
|
std::uint8_t initial;
|
||||||
|
};
|
||||||
|
std::vector<Frame> frames;
|
||||||
|
for (std::uint64_t i = 0; i < 101; ++i)
|
||||||
|
frames.push_back({i, i, static_cast<double>(i), i ? 1. : 0.,
|
||||||
|
static_cast<std::uint8_t>(i == 0)});
|
||||||
|
if (defect == 6) frames[37].frame = 36;
|
||||||
|
if (defect == 7) frames.pop_back();
|
||||||
|
if (defect == 8) frames.push_back(frames.back());
|
||||||
|
Handle ft{H5Tcreate(H5T_COMPOUND, sizeof(Frame)), H5Tclose};
|
||||||
|
H5Tinsert(ft.id, "frame_index", HOFFSET(Frame, frame), H5T_STD_U64LE);
|
||||||
|
H5Tinsert(ft.id, "increment_index", HOFFSET(Frame, increment), H5T_STD_U64LE);
|
||||||
|
H5Tinsert(ft.id, "time", HOFFSET(Frame, time), H5T_IEEE_F64LE);
|
||||||
|
H5Tinsert(ft.id, "time_increment", HOFFSET(Frame, delta), H5T_IEEE_F64LE);
|
||||||
|
H5Tinsert(ft.id, "is_initial", HOFFSET(Frame, initial), H5T_STD_U8LE);
|
||||||
|
Dataset(file.id, "/steps/Step-1/accepted_frames", ft.id, {frames.size()},
|
||||||
|
frames.data());
|
||||||
|
Handle group{H5Gopen2(file.id, "/steps/Step-1/frames/37", H5P_DEFAULT),
|
||||||
|
H5Gclose};
|
||||||
|
ScalarAttribute<double>(group.id, "time", H5T_IEEE_F64LE,
|
||||||
|
defect == 12 ? 38. : 37.);
|
||||||
|
ScalarAttribute<std::uint64_t>(group.id, "frame_index", H5T_STD_U64LE, 37);
|
||||||
|
ScalarAttribute<std::uint64_t>(group.id, "increment_index", H5T_STD_U64LE,
|
||||||
|
37);
|
||||||
|
ScalarAttribute<std::uint8_t>(group.id, "is_initial", H5T_STD_U8LE, 0);
|
||||||
|
double values[10] = {1000, 1, 2, 3, 4, -15.5742216, 5, 6, 7, 8};
|
||||||
|
if (defect == 5) values[8] = std::numeric_limits<double>::quiet_NaN();
|
||||||
|
Dataset(file.id, "/steps/Step-1/frames/37/nodal/temperature",
|
||||||
|
defect == 4 ? H5T_STD_U64LE : H5T_IEEE_F64LE,
|
||||||
|
{2, defect == 3 ? 4U : 5U}, values);
|
||||||
|
Handle temperature{
|
||||||
|
H5Dopen2(file.id, "/steps/Step-1/frames/37/nodal/temperature",
|
||||||
|
H5P_DEFAULT),
|
||||||
|
H5Dclose};
|
||||||
|
StringAttribute(
|
||||||
|
temperature.id, "component_order",
|
||||||
|
defect == 13 ? "NT15,NT14,NT13,NT12,NT11" : "NT11,NT12,NT13,NT14,NT15");
|
||||||
|
StringAttribute(temperature.id, "units_label", "temperature");
|
||||||
|
StringAttribute(temperature.id, "coordinate_system", "scalar");
|
||||||
|
StringAttribute(temperature.id, "location", "nodal-thickness-stations");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ThermalReferenceComparison,
|
||||||
|
HdfSchemaMappingRejectsMalformedStationFrameAndNodeData) {
|
||||||
|
TemporaryFolder folder;
|
||||||
|
const auto path = folder.path / "fixture.h5";
|
||||||
|
for (int defect = 0; defect <= 14; ++defect) {
|
||||||
|
SCOPED_TRACE(defect);
|
||||||
|
HdfFixture(path, defect);
|
||||||
|
const auto read = fesa::test::ReadThermalReferenceHdf5(path);
|
||||||
|
if (defect == 0 || defect == 10 || defect == 11) {
|
||||||
|
ASSERT_TRUE(read.HasValue())
|
||||||
|
<< (read.HasValue() ? "" : read.GetStatus().Diagnostics()[0].message);
|
||||||
|
ThermalReferenceSnapshot reference{
|
||||||
|
"Step-1",
|
||||||
|
37,
|
||||||
|
37,
|
||||||
|
11,
|
||||||
|
{{"Part-1-1", 7, -15.5742216}, {"Part-1-1", 20, 1000}}};
|
||||||
|
const auto result = fesa::test::CompareThermalSnapshots(
|
||||||
|
read.Value(), reference, {7, 20}, "Part-1-1");
|
||||||
|
EXPECT_EQ(result.HasValue(), defect == 0);
|
||||||
|
if (result.HasValue()) EXPECT_TRUE(result.Value().passed);
|
||||||
|
} else
|
||||||
|
EXPECT_FALSE(read.HasValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ThermalReferenceSnapshot Snapshot() {
|
||||||
|
return {"Step-1",
|
||||||
|
37,
|
||||||
|
37,
|
||||||
|
11,
|
||||||
|
{{"Part-1-1", 7, -15.5742216},
|
||||||
|
{"Part-1-1", 20, 1000},
|
||||||
|
{"Part-1-1", 3, 0}}};
|
||||||
|
}
|
||||||
|
TEST(ThermalReferenceComparison,
|
||||||
|
MapsSourceLabelsWithoutRowOrderAndPreservesSignedValues) {
|
||||||
|
auto actual = Snapshot(), reference = Snapshot();
|
||||||
|
std::reverse(actual.rows.begin(), actual.rows.end());
|
||||||
|
const auto result = fesa::test::CompareThermalSnapshots(
|
||||||
|
actual, reference, {3, 7, 20}, "Part-1-1");
|
||||||
|
ASSERT_TRUE(result.HasValue());
|
||||||
|
EXPECT_TRUE(result.Value().passed);
|
||||||
|
ASSERT_EQ(result.Value().rows.size(), 3U);
|
||||||
|
EXPECT_EQ(result.Value().rows[1].label, 7U);
|
||||||
|
EXPECT_EQ(result.Value().rows[1].reference, -15.5742216);
|
||||||
|
EXPECT_EQ(result.Value().rows[1].tolerance.branch,
|
||||||
|
fesa::test::ReferenceToleranceBranch::kRelative);
|
||||||
|
EXPECT_EQ(result.Value().family.reference_scale, 1000);
|
||||||
|
EXPECT_EQ(result.Value().family.near_zero_band, 10);
|
||||||
|
EXPECT_FALSE(result.Value().family.blocking);
|
||||||
|
}
|
||||||
|
TEST(ThermalReferenceComparison,
|
||||||
|
RejectsMissingExtraDuplicateAndNonfiniteBeforeTolerance) {
|
||||||
|
for (int mode = 0; mode < 5; ++mode) {
|
||||||
|
for (bool mutate_actual : {false, true}) {
|
||||||
|
auto actual = Snapshot(), reference = Snapshot();
|
||||||
|
auto& rows = mutate_actual ? actual.rows : reference.rows;
|
||||||
|
switch (mode) {
|
||||||
|
case 0:
|
||||||
|
rows.pop_back();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
rows.push_back({"Part-1-1", 99, 0});
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
rows.push_back(rows[0]);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
rows[0].value = std::numeric_limits<double>::infinity();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
rows[0].value = std::numeric_limits<double>::quiet_NaN();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
SCOPED_TRACE(mode);
|
||||||
|
SCOPED_TRACE(mutate_actual);
|
||||||
|
EXPECT_FALSE(fesa::test::CompareThermalSnapshots(actual, reference,
|
||||||
|
{3, 7, 20}, "Part-1-1")
|
||||||
|
.HasValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TEST(ThermalReferenceComparison,
|
||||||
|
RejectsWrongStepTimeIncrementInstanceAndStation) {
|
||||||
|
for (int mode = 0; mode < 5; ++mode) {
|
||||||
|
for (bool mutate_actual : {false, true}) {
|
||||||
|
auto actual = Snapshot(), reference = Snapshot();
|
||||||
|
auto& row = mutate_actual ? actual : reference;
|
||||||
|
switch (mode) {
|
||||||
|
case 0:
|
||||||
|
row.step = "Step-2";
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
row.time = std::nextafter(37., 38.);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
row.increment = 38;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
row.rows[0].instance = "Other";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
row.source_dof = 15;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
SCOPED_TRACE(mode);
|
||||||
|
SCOPED_TRACE(mutate_actual);
|
||||||
|
EXPECT_FALSE(fesa::test::CompareThermalSnapshots(actual, reference,
|
||||||
|
{3, 7, 20}, "Part-1-1")
|
||||||
|
.HasValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TEST(ThermalReferenceComparison,
|
||||||
|
UsesUnmodifiedFivePercentOnePercentAndExactZeroPolicy) {
|
||||||
|
auto actual = Snapshot(), reference = Snapshot();
|
||||||
|
actual.rows[0].value = 0;
|
||||||
|
auto result = fesa::test::CompareThermalSnapshots(actual, reference,
|
||||||
|
{3, 7, 20}, "Part-1-1");
|
||||||
|
ASSERT_TRUE(result.HasValue());
|
||||||
|
EXPECT_FALSE(result.Value().passed);
|
||||||
|
for (auto& row : reference.rows) row.value = 0;
|
||||||
|
actual = reference;
|
||||||
|
actual.rows[0].value = std::numeric_limits<double>::denorm_min();
|
||||||
|
result = fesa::test::CompareThermalSnapshots(actual, reference, {3, 7, 20},
|
||||||
|
"Part-1-1");
|
||||||
|
ASSERT_TRUE(result.HasValue());
|
||||||
|
EXPECT_FALSE(result.Value().passed);
|
||||||
|
EXPECT_EQ(result.Value().family.reference_scale, 0);
|
||||||
|
}
|
||||||
|
TEST(ThermalReferenceComparison,
|
||||||
|
CsvRequiresExactHeaderFrameFiniteValuesAndSoleInstance) {
|
||||||
|
static std::atomic<unsigned> next{0};
|
||||||
|
std::filesystem::path directory;
|
||||||
|
do {
|
||||||
|
directory = std::filesystem::temp_directory_path() /
|
||||||
|
("fesa-thermal-csv-" + std::to_string(std::random_device{}()) +
|
||||||
|
"-" + std::to_string(next++));
|
||||||
|
} while (!std::filesystem::create_directory(directory));
|
||||||
|
const auto path = directory / "fixture.csv";
|
||||||
|
const std::string header = "Step,Frame,Node Label,NT11\n";
|
||||||
|
const std::string valid =
|
||||||
|
"Step-1,Increment 37: Step Time = 37.00,7,-15.5742216\n";
|
||||||
|
for (const auto& text :
|
||||||
|
{header + valid, std::string{"Step,Frame,Node Label,NT15\n"} + valid,
|
||||||
|
header + "Step-1,Increment 38: Step Time = 37.00,7,0\n",
|
||||||
|
header + "Step-2,Increment 37: Step Time = 37.00,7,0\n",
|
||||||
|
header + "Step-1,Increment 37: Step Time = 37.00,7,nan\n",
|
||||||
|
header + "Step-1,Increment 37: Step Time = 37.00,7,0,extra\n"}) {
|
||||||
|
{
|
||||||
|
std::ofstream out{path};
|
||||||
|
out << text;
|
||||||
|
}
|
||||||
|
const auto result = fesa::test::ReadThermalReferenceCsv(path, "Part-1-1");
|
||||||
|
EXPECT_EQ(result.HasValue(), text == header + valid);
|
||||||
|
EXPECT_FALSE(fesa::test::ReadThermalReferenceCsv(path, "").HasValue());
|
||||||
|
}
|
||||||
|
std::filesystem::remove_all(directory);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
Reference in New Issue
Block a user