test: add immutable shell Jet baseline oracle
This commit is contained in:
@@ -0,0 +1,561 @@
|
||||
// Standalone private baseline fixture: compile this translation unit instead of
|
||||
// nonlinear_mitc4_shell.cpp, then link the ordinary solver archive. No
|
||||
// production source, public interface, or official CLI instrumentation is
|
||||
// needed. Build target: fesa_jet_baseline_tests (MSBuild/CMake). --verify runs
|
||||
// independent checks without writing evidence. Compare immutable evidence with
|
||||
// --compare PATH, Debug to Debug or Release to Release; never regenerate a
|
||||
// snapshot during regression. See nonlinear_mitc4_shell_jet_baseline.md.
|
||||
#include <float.h>
|
||||
#include <xmmintrin.h>
|
||||
|
||||
#include <cfenv>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <future>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include "../../../src/fesa/elements/nonlinear_mitc4_shell.cpp"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
using Coefficients = std::array<double, 24>;
|
||||
std::fenv_t initial_environment;
|
||||
unsigned initial_mxcsr;
|
||||
|
||||
std::string FpState() {
|
||||
unsigned control = 0;
|
||||
_controlfp_s(&control, 0, 0);
|
||||
std::ostringstream out;
|
||||
out << std::fetestexcept(FE_ALL_EXCEPT) << ':' << std::fegetround() << ':'
|
||||
<< _statusfp() << ':' << control << ':' << _mm_getcsr();
|
||||
return out.str();
|
||||
}
|
||||
|
||||
std::string ResetFp() {
|
||||
std::fesetenv(&initial_environment);
|
||||
std::feclearexcept(FE_ALL_EXCEPT);
|
||||
_clearfp();
|
||||
_mm_setcsr(initial_mxcsr & ~0x3fu);
|
||||
return FpState();
|
||||
}
|
||||
|
||||
struct Monomial {
|
||||
double coefficient;
|
||||
std::array<unsigned, 24> powers{};
|
||||
};
|
||||
|
||||
// Independent oracle: differentiate monomial exponent lists, never Jet rules.
|
||||
double Differentiate(const std::vector<Monomial>& polynomial,
|
||||
const Coefficients& point,
|
||||
const std::vector<std::size_t>& derivative) {
|
||||
long double sum = 0;
|
||||
for (auto term : polynomial) {
|
||||
long double value = term.coefficient;
|
||||
for (auto index : derivative) {
|
||||
value *= term.powers[index];
|
||||
if (term.powers[index] == 0) {
|
||||
value = 0;
|
||||
break;
|
||||
}
|
||||
--term.powers[index];
|
||||
}
|
||||
for (std::size_t i = 0; i < 24; ++i)
|
||||
for (unsigned p = 0; p < term.powers[i]; ++p) value *= point[i];
|
||||
sum += value;
|
||||
}
|
||||
return static_cast<double>(sum);
|
||||
}
|
||||
|
||||
std::uint64_t Bits(double value) {
|
||||
std::uint64_t result;
|
||||
std::memcpy(&result, &value, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
void Check(double actual, double expected, const char* channel) {
|
||||
if (!std::isfinite(actual) ||
|
||||
std::abs(actual - expected) > 2e-12 * std::max(1.0, std::abs(expected)))
|
||||
throw std::runtime_error(std::string("independent oracle: ") + channel);
|
||||
}
|
||||
Jet Polynomial(const std::vector<Monomial>& terms,
|
||||
const std::array<Jet, 24>& variables) {
|
||||
Jet result = Constant(0);
|
||||
for (const auto& term : terms) {
|
||||
Jet value = Constant(term.coefficient);
|
||||
for (std::size_t i = 0; i < 24; ++i)
|
||||
for (unsigned p = 0; p < term.powers[i]; ++p)
|
||||
value = value * variables[i];
|
||||
result = result + value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
std::string Snapshot(const char* name, const Jet& jet, int exceptions,
|
||||
unsigned status, unsigned mxcsr) {
|
||||
std::ostringstream out;
|
||||
out << name << ' ' << jet.constant_derivatives << ' '
|
||||
<< jet.second.IsImplicitZero() << ' ' << IsFinite(jet) << ' '
|
||||
<< exceptions << ' ' << status << ' ' << mxcsr << std::hex;
|
||||
auto put = [&](double x) {
|
||||
if (std::isnan(x))
|
||||
out << " nan";
|
||||
else
|
||||
out << ' ' << Bits(x);
|
||||
};
|
||||
put(jet.value);
|
||||
for (auto x : jet.first) put(x);
|
||||
for (auto x : jet.second) put(x);
|
||||
put(jet.directional_first);
|
||||
put(jet.directional_second);
|
||||
for (auto x : jet.mixed_first) put(x);
|
||||
for (auto x : jet.mixed_second) put(x);
|
||||
return out.str() + "\n";
|
||||
}
|
||||
template <class F>
|
||||
std::string Observe(const char* name, F expression, Jet* returned = nullptr) {
|
||||
const auto before = ResetFp();
|
||||
Jet value = expression();
|
||||
const int exceptions = std::fetestexcept(FE_ALL_EXCEPT);
|
||||
const unsigned status = _statusfp();
|
||||
const unsigned mxcsr = _mm_getcsr();
|
||||
const auto after = FpState();
|
||||
if (returned != nullptr) *returned = value;
|
||||
return std::string("initial-fp=") + before + " final-fp=" + after + " " +
|
||||
Snapshot(name, value, exceptions, status, mxcsr);
|
||||
}
|
||||
|
||||
std::string Verify(bool corrupt_oracle) {
|
||||
Coefficients x{}, direction{};
|
||||
std::array<Jet, 24> variables{};
|
||||
std::vector<Monomial> terms;
|
||||
for (std::size_t i = 0; i < 24; ++i) {
|
||||
x[i] = (static_cast<double>(i % 7) - 3) / 8;
|
||||
direction[i] = (static_cast<double>(i % 5) - 2) / 16;
|
||||
variables[i] = Variable(x[i], i, direction[i]);
|
||||
Monomial term{(i % 2 ? -1.0 : 1.0) / 8};
|
||||
term.powers[i] = 3;
|
||||
term.powers[(i + 1) % 24] += 2;
|
||||
terms.push_back(term);
|
||||
Monomial cross{0.5};
|
||||
cross.powers[i] = 1;
|
||||
cross.powers[(i + 11) % 24] += 1;
|
||||
terms.push_back(cross);
|
||||
}
|
||||
Jet polynomial = Polynomial(terms, variables);
|
||||
if (corrupt_oracle) polynomial.value += 1; // Deliberate fixture-only mutant.
|
||||
Check(polynomial.value, Differentiate(terms, x, {}), "value");
|
||||
double d1 = 0, d2 = 0;
|
||||
for (std::size_t i = 0; i < 24; ++i) {
|
||||
const double gradient = Differentiate(terms, x, {i});
|
||||
Check(polynomial.first[i], gradient, "gradient");
|
||||
d1 += gradient * direction[i];
|
||||
double mixed1 = 0, mixed2 = 0;
|
||||
for (std::size_t j = 0; j < 24; ++j) {
|
||||
const double hessian = Differentiate(terms, x, {i, j});
|
||||
Check(polynomial.second.ReadData()[24 * i + j], hessian, "Hessian");
|
||||
d2 += hessian * direction[i] * direction[j];
|
||||
mixed1 += hessian * direction[j];
|
||||
for (std::size_t k = 0; k < 24; ++k)
|
||||
mixed2 +=
|
||||
Differentiate(terms, x, {i, j, k}) * direction[j] * direction[k];
|
||||
}
|
||||
Check(polynomial.mixed_first[i], mixed1, "mixed_first");
|
||||
Check(polynomial.mixed_second[i], mixed2, "mixed_second");
|
||||
}
|
||||
Check(polynomial.directional_first, d1, "directional_first");
|
||||
Check(polynomial.directional_second, d2, "directional_second");
|
||||
// Affine composition has a rank-one Hessian; its third derivative contracts
|
||||
// independently to the mixed-second channel.
|
||||
Jet affine = Constant(2);
|
||||
Coefficients weights{};
|
||||
double slope = 0;
|
||||
for (std::size_t i = 0; i < 24; ++i) {
|
||||
weights[i] = (static_cast<double>(i % 3) - 1) / 64;
|
||||
affine = affine + weights[i] * variables[i];
|
||||
slope += weights[i] * direction[i];
|
||||
}
|
||||
auto unary = [&](const Jet& jet, double value, double a, double b, double c) {
|
||||
Check(jet.value, value, "unary value");
|
||||
Check(jet.directional_first, a * slope, "unary direction1");
|
||||
Check(jet.directional_second, b * slope * slope, "unary direction2");
|
||||
for (std::size_t i = 0; i < 24; ++i) {
|
||||
Check(jet.first[i], a * weights[i], "unary gradient");
|
||||
Check(jet.mixed_first[i], b * weights[i] * slope, "unary mixed1");
|
||||
Check(jet.mixed_second[i], c * weights[i] * slope * slope,
|
||||
"unary mixed2");
|
||||
for (std::size_t j = 0; j < 24; ++j)
|
||||
Check(jet.second.ReadData()[24 * i + j], b * weights[i] * weights[j],
|
||||
"unary Hessian");
|
||||
}
|
||||
};
|
||||
const double t = affine.value, r = std::sqrt(t);
|
||||
unary(Sin(affine), std::sin(t), std::cos(t), -std::sin(t), -std::cos(t));
|
||||
unary(Cos(affine), std::cos(t), -std::sin(t), -std::cos(t), std::sin(t));
|
||||
unary(Reciprocal(affine), 1 / t, -1 / (t * t), 2 / (t * t * t),
|
||||
-6 / (t * t * t * t));
|
||||
unary(Sqrt(affine), r, 0.5 / r, -0.25 / (r * r * r),
|
||||
0.375 / (r * r * r * r * r));
|
||||
const double den = 1 + t * t;
|
||||
unary(Atan2(affine, Constant(1)), std::atan(t), 1 / den, -2 * t / (den * den),
|
||||
(6 * t * t - 2) / (den * den * den));
|
||||
const Jet original = polynomial;
|
||||
const Jet alias = original;
|
||||
const auto before = Snapshot("alias", alias, 0, 0, 0);
|
||||
const Jet transformed = alias * alias + original;
|
||||
if (before != Snapshot("alias", original, 0, 0, 0))
|
||||
throw std::runtime_error("alias modified");
|
||||
std::string out;
|
||||
std::vector<std::future<std::pair<Jet, std::string>>> futures;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
futures.push_back(std::async(std::launch::async, [&] {
|
||||
Jet value;
|
||||
const auto evidence = Observe(
|
||||
"shared-full", [&] { return original * original + original; },
|
||||
&value);
|
||||
return std::make_pair(std::move(value), evidence);
|
||||
}));
|
||||
for (std::size_t i = 0; i < futures.size(); ++i) {
|
||||
const auto worker = futures[i].get();
|
||||
if (Snapshot("shared", worker.first, 0, 0, 0) !=
|
||||
Snapshot("shared", transformed, 0, 0, 0))
|
||||
throw std::runtime_error("shared complete-channel snapshot");
|
||||
out += "worker-" + std::to_string(i) + " " + worker.second;
|
||||
}
|
||||
auto add = [&](const char* name, auto f) { out += Observe(name, f); };
|
||||
add("constant", [] { return Constant(-0.0); });
|
||||
add("variable-zero-direction", [] { return Variable(-0.0, 23); });
|
||||
add("variable-direction", [] { return Variable(0.25, 0, -0.5); });
|
||||
add("polynomial", [&] { return Polynomial(terms, variables); });
|
||||
add("polynomial-zero-direction", [&] {
|
||||
auto zero_direction = variables;
|
||||
for (auto& variable : zero_direction) variable.directional_first = 0;
|
||||
return Polynomial(terms, zero_direction);
|
||||
});
|
||||
add("actual-zero-hessian", [&] { return original - original; });
|
||||
add("implicit-zero-nonconstant",
|
||||
[] { return Variable(1, 0) - Variable(1, 0); });
|
||||
add("implicit-add", [] { return Variable(1, 0) + Constant(-1); });
|
||||
add("implicit-subtract", [] { return Variable(1, 0) - Constant(1); });
|
||||
for (int kind = 0; kind < 4; ++kind) {
|
||||
const auto name = "surviving-copy-" + std::to_string(kind);
|
||||
add(name.c_str(), [&] {
|
||||
Jet survivor;
|
||||
std::string expected;
|
||||
{
|
||||
Jet source = kind == 0 ? Constant(-0.0)
|
||||
: kind == 1 ? Variable(0.5, 7, -0.25)
|
||||
: Polynomial(terms, variables);
|
||||
if (kind == 3) source = source - source;
|
||||
const Jet another_alias = source;
|
||||
expected = Snapshot("survivor", source, 0, 0, 0);
|
||||
survivor = another_alias;
|
||||
}
|
||||
if (Snapshot("survivor", survivor, 0, 0, 0) != expected)
|
||||
throw std::runtime_error("surviving copy complete-channel snapshot");
|
||||
return survivor;
|
||||
});
|
||||
}
|
||||
add("constant-multiply", [] { return Constant(-0.0) * Constant(3); });
|
||||
add("constant-compose", [] { return Sin(Constant(0)); });
|
||||
add("scale", [&] { return -0.5 * original; });
|
||||
add("multiply", [&] { return original * original; });
|
||||
add("compose-sin", [&] { return Sin(original); });
|
||||
add("compose-cos", [&] { return Cos(original); });
|
||||
add("divide", [&] { return original / affine; });
|
||||
add("sqrt", [&] { return Sqrt(affine); });
|
||||
add("atan2", [&] { return Atan2(original, affine); });
|
||||
add("dot", [&] {
|
||||
return Dot(JetVector3{original, affine, variables[0]},
|
||||
JetVector3{affine, variables[1], original});
|
||||
});
|
||||
add("cancellation",
|
||||
[] { return (Variable(1e16, 0, 1) + Constant(1)) - Constant(1e16); });
|
||||
add("overflow", [] {
|
||||
return Variable(std::numeric_limits<double>::max(), 0, 1) * Constant(2);
|
||||
});
|
||||
add("subnormal", [] {
|
||||
return std::numeric_limits<double>::denorm_min() * Variable(0.5, 0, 0.5);
|
||||
});
|
||||
add("nan", [] {
|
||||
return Constant(std::numeric_limits<double>::quiet_NaN()) * Variable(1, 0);
|
||||
});
|
||||
add("inf", [] {
|
||||
return Constant(std::numeric_limits<double>::infinity()) * Constant(0);
|
||||
});
|
||||
add("singular", [] { return Reciprocal(Variable(0, 0, 1)); });
|
||||
add("singular-zero-direction", [] { return Reciprocal(Variable(0, 0)); });
|
||||
add("constant-singular-reciprocal", [] { return Reciprocal(Constant(0)); });
|
||||
add("constant-singular-sqrt", [] { return Sqrt(Constant(0)); });
|
||||
add("negative-sqrt", [] { return Sqrt(Variable(-1, 0, 1)); });
|
||||
add("after-failure", [&] { return Polynomial(terms, variables); });
|
||||
// Exercise the production MKL k=1 outer path directly, including entries
|
||||
// whose rounded bits agree while their IEEE exception state can differ.
|
||||
auto outer = [](double a, double b) {
|
||||
Jet jet{};
|
||||
Coefficients left{}, right{};
|
||||
for (std::size_t i = 0; i < 24; ++i) {
|
||||
left[i] = i % 2 ? -a : a;
|
||||
right[i] = i % 3 ? -b : b;
|
||||
}
|
||||
Outer(left, right, jet.second.OverwriteData());
|
||||
return jet;
|
||||
};
|
||||
add("outer-signed-zero", [&] { return outer(-0.0, 1); });
|
||||
add("outer-underflow",
|
||||
[&] { return outer(std::numeric_limits<double>::denorm_min(), 0.5); });
|
||||
add("outer-overflow",
|
||||
[&] { return outer(std::numeric_limits<double>::max(), 2); });
|
||||
add("outer-invalid",
|
||||
[&] { return outer(std::numeric_limits<double>::infinity(), 0); });
|
||||
if (!Constant(1).constant_derivatives ||
|
||||
!Constant(1).second.IsImplicitZero() ||
|
||||
Variable(1, 0).constant_derivatives ||
|
||||
!Variable(1, 0).second.IsImplicitZero() ||
|
||||
polynomial.second.IsImplicitZero())
|
||||
throw std::runtime_error("provenance");
|
||||
return out;
|
||||
}
|
||||
|
||||
// Public request-mode evidence for later direction-channel separation. Existing
|
||||
// tangent tests check nonzero-direction derivatives but do not freeze the full
|
||||
// omitted-direction response inventory and invalid-direction diagnostics.
|
||||
std::string PublicSnapshots() {
|
||||
std::array<Node, 4> nodes;
|
||||
const std::array<std::array<double, 3>, 4> coordinates{
|
||||
{{{-1, -1, 0}}, {{1, -1, 0}}, {{1, 1, 0}}, {{-1, 1, 0}}}};
|
||||
std::array<const Node*, 4> pointers{};
|
||||
std::array<ShellNodeInitialFrame, 4> frames{};
|
||||
NonlinearMitc4TrialDofs trial{};
|
||||
Vector zero(24), direction(24), wrong(23), nan(24), inf(24);
|
||||
for (std::size_t i = 0; i < 4; ++i) {
|
||||
nodes[i] = {{"Jet-Baseline", static_cast<std::int64_t>(i + 1),
|
||||
std::to_string(i + 1)},
|
||||
coordinates[i],
|
||||
{"jet-baseline.inp", i + 1}};
|
||||
pointers[i] = &nodes[i];
|
||||
frames[i] = {static_cast<EntityIndex>(i), {0, 0, 1}, {1, 0, 0}, {0, 1, 0}};
|
||||
trial.displacement[i] = {0.01 * i, -0.02 * i, 0.015 * i};
|
||||
trial.rotation_vector[i] = {0.02 * i, -0.01 * i, 0.005 * i};
|
||||
}
|
||||
for (std::size_t i = 0; i < 24; ++i)
|
||||
direction[i] = (static_cast<double>(i % 5) - 2) / 8;
|
||||
nan[7] = std::numeric_limits<double>::quiet_NaN();
|
||||
inf[7] = std::numeric_limits<double>::infinity();
|
||||
const auto created = NonlinearMitc4Shell::Create(
|
||||
pointers, frames, {"Section", 0.2, 0, {"jet-baseline.inp", 20}},
|
||||
{"Material", 120, 0.25, {"jet-baseline.inp", 21}});
|
||||
if (!created.HasValue()) throw std::runtime_error("public fixture creation");
|
||||
std::ostringstream out;
|
||||
for (const auto mode : {Mitc4DrillingMode::kDirectSpring,
|
||||
Mitc4DrillingMode::kLegacyCorotatedTwist}) {
|
||||
const auto shell =
|
||||
mitc4_drilling_internal::TestAccess::WithMode(created.Value(), mode);
|
||||
auto observe = [&](const char* name, const NonlinearMitc4TrialDofs& input,
|
||||
const Vector* request,
|
||||
std::optional<bool> expected_success) {
|
||||
const auto before = ResetFp();
|
||||
const auto result =
|
||||
EvaluateInternalResidualAndTangent(shell, input, request);
|
||||
const int exceptions = std::fetestexcept(FE_ALL_EXCEPT);
|
||||
const unsigned status = _statusfp(), mxcsr = _mm_getcsr();
|
||||
if (expected_success && result.HasValue() != *expected_success)
|
||||
throw std::runtime_error(std::string("public outcome: ") + name);
|
||||
out << "public " << static_cast<int>(mode) << ' ' << name << ' '
|
||||
<< result.HasValue() << ' ' << exceptions << ' ' << status << ' '
|
||||
<< mxcsr << " initial-fp=" << before << " final-fp=" << FpState();
|
||||
auto put = [&](double value) {
|
||||
if (!std::isfinite(value))
|
||||
throw std::runtime_error("public nonfinite field");
|
||||
out << ' ' << std::hex << Bits(value) << std::dec;
|
||||
};
|
||||
auto vector = [&](const Vector& value) {
|
||||
out << " v" << value.Size();
|
||||
for (std::size_t i = 0; i < value.Size(); ++i) put(value[i]);
|
||||
};
|
||||
auto matrix = [&](const Matrix& value) {
|
||||
out << " m" << value.Rows() << 'x' << value.Columns();
|
||||
for (std::size_t i = 0; i < value.Rows() * value.Columns(); ++i)
|
||||
put(value.Data()[i]);
|
||||
};
|
||||
if (result.HasValue()) {
|
||||
const auto& r = result.Value();
|
||||
vector(r.internal_residual);
|
||||
matrix(r.internal_tangent);
|
||||
vector(r.internal_force);
|
||||
put(r.physical_strain_energy);
|
||||
vector(r.physical_force);
|
||||
vector(r.drilling_force);
|
||||
matrix(r.drilling_tangent);
|
||||
matrix(r.drilling_metric);
|
||||
put(r.drilling_energy);
|
||||
vector(r.force_directional_second);
|
||||
vector(r.drilling_force_directional_second);
|
||||
vector(r.drilling_constraint);
|
||||
matrix(r.drilling_constraint_gradient);
|
||||
for (const auto& hessian : r.drilling_constraint_hessian)
|
||||
matrix(hessian);
|
||||
for (const auto& third :
|
||||
r.drilling_constraint_third_directional_gradient)
|
||||
vector(third);
|
||||
put(r.recovery.physical_strain_energy);
|
||||
for (const auto& point : r.recovery.gauss_points) {
|
||||
out << " location=" << static_cast<int>(point.location);
|
||||
for (double value : point.natural_coordinates) put(value);
|
||||
for (const auto& axis : {point.local_frame.e1, point.local_frame.e2,
|
||||
point.local_frame.e3})
|
||||
for (double value : axis) put(value);
|
||||
for (double value : point.generalized_strain) put(value);
|
||||
for (double value : point.section_resultant) put(value);
|
||||
for (const auto& stress : point.stress) {
|
||||
out << " section=" << static_cast<int>(stress.position);
|
||||
put(stress.zeta);
|
||||
for (double value : stress.components) put(value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const auto& failure = result.GetStatus();
|
||||
out << " category="
|
||||
<< (failure.Category() ? static_cast<int>(*failure.Category())
|
||||
: -1);
|
||||
for (const auto& diagnostic : failure.Diagnostics())
|
||||
out << ' ' << static_cast<int>(diagnostic.severity) << ' '
|
||||
<< std::quoted(diagnostic.code) << ' '
|
||||
<< std::quoted(diagnostic.location.file.generic_string()) << ' '
|
||||
<< diagnostic.location.line << ' '
|
||||
<< std::quoted(diagnostic.keyword) << ' '
|
||||
<< std::quoted(diagnostic.entity_identity) << ' '
|
||||
<< std::quoted(diagnostic.message);
|
||||
}
|
||||
out << '\n';
|
||||
};
|
||||
observe("none", trial, nullptr, true);
|
||||
observe("zero", trial, &zero, true);
|
||||
observe("nonzero", trial, &direction, true);
|
||||
observe("invalid-dimension", trial, &wrong, false);
|
||||
observe("nan-direction", trial, &nan, false);
|
||||
observe("inf-direction", trial, &inf, false);
|
||||
auto invalid = trial;
|
||||
invalid.displacement[1] = {std::numeric_limits<double>::quiet_NaN(), 0, 0};
|
||||
observe("nan-trial-none", invalid, nullptr, false);
|
||||
observe("none-after-failure", trial, nullptr, true);
|
||||
auto requests = [&](const std::string& name,
|
||||
const NonlinearMitc4TrialDofs& input) {
|
||||
observe((name + "-none").c_str(), input, nullptr, std::nullopt);
|
||||
observe((name + "-zero").c_str(), input, &zero, std::nullopt);
|
||||
observe((name + "-nonzero").c_str(), input, &direction, std::nullopt);
|
||||
};
|
||||
requests("zero-rotation", NonlinearMitc4TrialDofs{});
|
||||
for (const double threshold : {1e-8, 0.25}) {
|
||||
const double angle = std::sqrt(threshold);
|
||||
const std::array<double, 3> angles{
|
||||
std::nextafter(angle, 0.0), angle,
|
||||
std::nextafter(angle, std::numeric_limits<double>::infinity())};
|
||||
for (std::size_t i = 0; i < angles.size(); ++i) {
|
||||
NonlinearMitc4TrialDofs boundary{};
|
||||
for (auto& rotation : boundary.rotation_vector)
|
||||
rotation = {angles[i], 0, 0};
|
||||
requests((threshold == 0.25 ? "rotation-squared-quarter-"
|
||||
: "rotation-squared-small-") +
|
||||
std::to_string(i),
|
||||
boundary);
|
||||
}
|
||||
}
|
||||
auto translated = trial;
|
||||
for (auto& displacement : translated.displacement)
|
||||
displacement = displacement + Vector3{1e9, -2e9, 3e9};
|
||||
requests("large-translation", translated);
|
||||
}
|
||||
return out.str();
|
||||
}
|
||||
|
||||
__declspec(noinline) double Consume(const Jet& value) {
|
||||
double result =
|
||||
value.value + value.directional_first + value.directional_second;
|
||||
for (double entry : value.first) result += entry;
|
||||
for (double entry : value.second) result += entry;
|
||||
for (double entry : value.mixed_first) result += entry;
|
||||
for (double entry : value.mixed_second) result += entry;
|
||||
return result + value.constant_derivatives + value.second.IsImplicitZero();
|
||||
}
|
||||
|
||||
void Bench() {
|
||||
volatile double consumed = 0;
|
||||
for (int kind = 0; kind < 8; ++kind) {
|
||||
Jet left =
|
||||
kind == 0 ? Constant(0.5) : Variable(0.5, 0, kind % 2 ? 0.0 : 0.25);
|
||||
Jet right = Variable(0.75, 1, kind == 0 || kind % 2 ? 0.0 : -0.5);
|
||||
if (kind >= 3) {
|
||||
left = left * left;
|
||||
right = right * right;
|
||||
}
|
||||
if (kind == 5) left = left - left;
|
||||
if (kind == 7) {
|
||||
left = Constant(0.5);
|
||||
right = Constant(0.75);
|
||||
}
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
for (int i = 0; i < 20000; ++i) {
|
||||
left.value += (i % 2 ? -1.0 : 1.0) / 1048576;
|
||||
const Jet value = kind == 6 ? Dot(JetVector3{left, right, left},
|
||||
JetVector3{right, left, right})
|
||||
: left * right;
|
||||
consumed = consumed + Consume(value);
|
||||
}
|
||||
std::cout << "bench," << kind << ",20000,"
|
||||
<< std::chrono::duration<double>(
|
||||
std::chrono::steady_clock::now() - start)
|
||||
.count()
|
||||
<< ",allocation-included," << consumed << '\n';
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
} // namespace fesa
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
std::fegetenv(&fesa::initial_environment);
|
||||
fesa::initial_mxcsr = _mm_getcsr();
|
||||
if (argc == 2 && std::string(argv[1]) == "--bench") {
|
||||
fesa::Bench();
|
||||
return 0;
|
||||
}
|
||||
const bool verify_only = argc == 2 && std::string(argv[1]) == "--verify";
|
||||
if (argc < 3 && !verify_only)
|
||||
throw std::runtime_error(
|
||||
"--verify OR --write|--compare PATH [--mutate|--corrupt-oracle]");
|
||||
std::string actual =
|
||||
fesa::Verify(argc > 3 && std::string(argv[3]) == "--corrupt-oracle");
|
||||
actual += fesa::PublicSnapshots();
|
||||
if (argc > 3 && std::string(argv[3]) == "--mutate")
|
||||
actual[actual.find('\n') - 1] ^= 1;
|
||||
if (verify_only) {
|
||||
// CTest and ordinary regression do not create baseline files.
|
||||
} else if (std::string(argv[1]) == "--write") {
|
||||
if (std::ifstream(argv[2], std::ios::binary))
|
||||
throw std::runtime_error("snapshot already exists");
|
||||
std::ofstream output(argv[2], std::ios::binary);
|
||||
output << actual;
|
||||
output.close();
|
||||
if (!output) throw std::runtime_error("snapshot write failure");
|
||||
} else if (std::string(argv[1]) == "--compare") {
|
||||
std::ifstream input(argv[2], std::ios::binary);
|
||||
const std::string expected((std::istreambuf_iterator<char>(input)), {});
|
||||
if (expected != actual)
|
||||
throw std::runtime_error("snapshot mismatch (bits/provenance/fenv)");
|
||||
} else {
|
||||
throw std::runtime_error("unknown verification mode");
|
||||
}
|
||||
std::cout
|
||||
<< argv[1]
|
||||
<< " passed; independent oracle and 42 private cases "
|
||||
"(651 floating channels each), 64 public request/error cases\n";
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
# Nonlinear MITC4 Jet 기준 검사
|
||||
|
||||
`fesa_jet_baseline_tests`는 제품의 private 구현을 별도 번역 단위에 포함하고
|
||||
일반 solver archive를 연결한다. 제품 source와 공개 API를 계측하지 않는다.
|
||||
독립 다항식 지수 미분 및 미분 항등식과 원래 경로의 비트 snapshot은 별도 근거다.
|
||||
|
||||
VS x64 환경과 저장소의 승인된 dependency 환경변수에서 실행한다.
|
||||
|
||||
```bat
|
||||
MSBuild msvc\fesa_jet_baseline_tests.vcxproj /m:2 /p:Configuration=Debug /p:Platform=x64
|
||||
.build\native\Debug\tests\fesa_jet_baseline_tests.exe --verify
|
||||
cmake --preset windows-msvc-ninja-debug
|
||||
cmake --build --preset windows-msvc-ninja-debug --target fesa_jet_baseline_tests --parallel 2
|
||||
ctest --preset windows-msvc-ninja-debug -R "^NonlinearMitc4Jet\.IndependentOracle$" --output-on-failure
|
||||
```
|
||||
|
||||
Release는 Native의 `Configuration=Release` 또는 해당 CMake Release preset을 쓴다.
|
||||
기존 unit/integration/reference 세 GTest 실행물의 발견 identity와 이 전용 검사의
|
||||
선택 수는 구분한다. CTest의 `--verify`는 baseline 파일을 생성하지 않는다.
|
||||
|
||||
승인된 기준 revision에서만 새 경로에 `--write <snapshot>`을 한 번 실행한다.
|
||||
기존 파일은 덮어쓰지 않는다. 이후에는 원래 파일과 hash를 보존하고 같은 빌드 경로와
|
||||
구성끼리 비교한다. 일반 회귀나 후보 코드에서 `--write`로 기대값을 만들지 않는다.
|
||||
|
||||
```bat
|
||||
.build\native\Debug\tests\fesa_jet_baseline_tests.exe --compare "<immutable-Debug-snapshot>"
|
||||
.build\native\Release\tests\fesa_jet_baseline_tests.exe --compare "<immutable-Release-snapshot>"
|
||||
.build\cmake-debug\tests\fesa_jet_baseline_tests.exe --compare "<immutable-CMake-Debug-snapshot>"
|
||||
```
|
||||
|
||||
`--compare <snapshot> --mutate`와 `--corrupt-oracle`는 각각 snapshot 비교와 독립
|
||||
oracle의 fixture 전용 오류 주입이며 exit 1이 기대값이다. `--bench`는 별도 micro
|
||||
측정이다. 연산의 할당·해제와 교대 입력값 갱신을 포함하고 fixture 생성은 제외한다.
|
||||
전체 채널을 읽는 noinline checksum과 volatile consume을 포함해 결과 제거를 막는다.
|
||||
공식 모델 CLI의 시간과 혼합하지 않는다.
|
||||
|
||||
snapshot은 finite 값·signed zero·Inf 비트, NaN 분류, provenance와 implicit Hessian,
|
||||
복원한 초기 FP 환경 및 연산 뒤 FE/rounding/control word/full MXCSR를 기록한다.
|
||||
공개 반환의 방향 요청 유무·실패 진단·회복량 identity도 포함한다. 수식의 정확성은
|
||||
독립 oracle로 검사하고, snapshot은 기준의 계산 순서와 관측 의미 보존을 검사한다.
|
||||
Reference in New Issue
Block a user