733 lines
31 KiB
C++
733 lines
31 KiB
C++
// 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;
|
|
}
|
|
|
|
// Separate supplemental evidence: never changes the original T1 snapshot.
|
|
// A changed Hessian association, a lost transpose term, or a changed MKL zero
|
|
// convention must fail the bit/classification and complete FP-state comparison.
|
|
std::string ProductSnapshots(bool omit_transpose) {
|
|
const std::array<double, 16> boundary{
|
|
0.0, -0.0, 1.0, -1.0, 0.1, -0.3,
|
|
std::numeric_limits<double>::denorm_min(),
|
|
-std::numeric_limits<double>::denorm_min(),
|
|
std::numeric_limits<double>::min(),
|
|
std::nextafter(std::numeric_limits<double>::min(), 0.0),
|
|
std::numeric_limits<double>::max(),
|
|
-std::numeric_limits<double>::max(),
|
|
std::numeric_limits<double>::infinity(),
|
|
-std::numeric_limits<double>::infinity(),
|
|
std::numeric_limits<double>::quiet_NaN(),
|
|
std::numeric_limits<double>::signaling_NaN()};
|
|
std::string out;
|
|
for (const int rounding : {FE_TONEAREST, FE_DOWNWARD, FE_UPWARD,
|
|
FE_TOWARDZERO}) {
|
|
for (const unsigned flush : {0u, 0x40u, 0x8000u, 0x8040u}) {
|
|
for (const bool sticky : {false, true}) {
|
|
auto reset = [&] {
|
|
ResetFp();
|
|
std::fesetround(rounding);
|
|
_mm_setcsr((_mm_getcsr() & ~0x8040u) | flush);
|
|
if (sticky) std::feraiseexcept(FE_ALL_EXCEPT);
|
|
return FpState();
|
|
};
|
|
for (std::size_t kind = 0; kind < 96; ++kind) {
|
|
ResetFp(); // Construct inputs outside the observed environment.
|
|
Jet left{}, right{};
|
|
left.value = 0.75;
|
|
right.value = -1.25;
|
|
left.directional_first = 0.25;
|
|
right.directional_first = -0.5;
|
|
left.directional_second = -0.125;
|
|
right.directional_second = 0.375;
|
|
std::uint64_t random = 0x719b245af51d6723ULL + kind;
|
|
auto coefficient = [&] {
|
|
random ^= random << 13;
|
|
random ^= random >> 7;
|
|
random ^= random << 17;
|
|
return (static_cast<double>(random % 2000001) - 1000000) /
|
|
1048576.0;
|
|
};
|
|
for (std::size_t i = 0; i < 24; ++i) {
|
|
left.first[i] = kind < 32 ? boundary[(kind + i) % 16]
|
|
: coefficient();
|
|
right.first[i] = kind < 32 ? boundary[(kind / 2 + 3 * i) % 16]
|
|
: coefficient();
|
|
left.mixed_first[i] = coefficient();
|
|
right.mixed_first[i] = coefficient();
|
|
left.mixed_second[i] = coefficient();
|
|
right.mixed_second[i] = coefficient();
|
|
}
|
|
// Include dense, asymmetric inputs, implicit zeros and aliased input.
|
|
if (kind % 3 != 0) {
|
|
double* l = left.second.OverwriteData();
|
|
double* r = right.second.OverwriteData();
|
|
for (std::size_t i = 0; i < 576; ++i) {
|
|
l[i] = coefficient();
|
|
r[i] = coefficient();
|
|
}
|
|
}
|
|
if (kind == 32) left = Constant(-0.0);
|
|
if (kind == 33) right = Constant(0.0);
|
|
if (kind == 34) left.value = boundary[10];
|
|
if (kind == 35) right.value = boundary[12];
|
|
if (kind == 36) left.second.Data()[17] = boundary[10];
|
|
if (kind == 37) right.second.Data()[24] = boundary[10];
|
|
if (kind == 38) left.second.Data()[17] = boundary[14];
|
|
if (kind == 39) right = left; // Shared immutable Hessian storage.
|
|
if (kind == 40) {
|
|
left.first[0] = boundary[12];
|
|
right.first[0] = boundary[15];
|
|
}
|
|
if (kind == 41) {
|
|
left.first[0] = boundary[15];
|
|
right.first[0] = boundary[12];
|
|
}
|
|
if (kind >= 42 && kind < 64) {
|
|
for (std::size_t i = 0; i < 24; ++i) {
|
|
left.first[i] = boundary[(kind + i) % 12];
|
|
right.first[i] = boundary[(kind / 2 + 3 * i) % 12];
|
|
if (kind == 42) {
|
|
left.first[i] = boundary[6];
|
|
right.first[i] = i % 2 ? -0.5 : 0.5;
|
|
}
|
|
if (kind == 43) {
|
|
left.first[i] = boundary[10];
|
|
right.first[i] = i % 2 ? -2.0 : 2.0;
|
|
}
|
|
if (kind == 44) {
|
|
left.first[i] = i % 2 ? -0.0 : 0.0;
|
|
right.first[i] = i % 3 ? -1.0 : 1.0;
|
|
}
|
|
}
|
|
}
|
|
const auto left_before = Snapshot("input", left, 0, 0, 0);
|
|
const auto right_before = Snapshot("input", right, 0, 0, 0);
|
|
const auto before = reset();
|
|
Jet product = left * right;
|
|
if (omit_transpose && kind >= 64) {
|
|
// Deliberately omit the reverse outer term in one finite entry.
|
|
// This mutant tests arithmetic detection, not just file integrity.
|
|
product.second.Data()[1] =
|
|
left.second.ReadData()[1] * right.value +
|
|
left.first[0] * right.first[1] +
|
|
left.value * right.second.ReadData()[1];
|
|
}
|
|
const int exceptions = std::fetestexcept(FE_ALL_EXCEPT);
|
|
const unsigned status = _statusfp(), mxcsr = _mm_getcsr();
|
|
const auto after = FpState();
|
|
out += "product " + std::to_string(kind) + " initial-fp=" + before +
|
|
" final-fp=" + after + " " +
|
|
Snapshot("result", product, exceptions, status, mxcsr);
|
|
if (left_before != Snapshot("input", left, 0, 0, 0) ||
|
|
right_before != Snapshot("input", right, 0, 0, 0))
|
|
throw std::runtime_error("supplemental product modified input");
|
|
|
|
// Independently pin the actual MKL operand-swap/transpose contract.
|
|
// Capture FP state before inspecting values (sNaN classification can
|
|
// itself raise INVALID). Each call starts from the same environment.
|
|
Jet forward{}, reverse{};
|
|
reset();
|
|
Outer(left.first, right.first, forward.second.OverwriteData());
|
|
const auto forward_fp = FpState();
|
|
reset();
|
|
Outer(right.first, left.first, reverse.second.OverwriteData());
|
|
const auto reverse_fp = FpState();
|
|
for (std::size_t row = 0; row < 24; ++row) {
|
|
for (std::size_t column = 0; column < 24; ++column) {
|
|
const double a = forward.second.ReadData()[row * 24 + column];
|
|
const double b = reverse.second.ReadData()[column * 24 + row];
|
|
if (!(std::isnan(a) && std::isnan(b)) && Bits(a) != Bits(b))
|
|
throw std::runtime_error("outer transpose coefficient mismatch");
|
|
}
|
|
}
|
|
out += "forward-outer-fp=" + forward_fp +
|
|
" reverse-outer-fp=" + reverse_fp + "\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ResetFp();
|
|
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;
|
|
}
|
|
if (argc >= 3 && (std::string(argv[1]) == "--product-write" ||
|
|
std::string(argv[1]) == "--product-compare")) {
|
|
auto actual = fesa::ProductSnapshots(
|
|
argc > 3 && std::string(argv[3]) == "--omit-transpose");
|
|
if (argc > 3 && std::string(argv[3]) == "--mutate")
|
|
actual[actual.find('\n') - 1] ^= 1;
|
|
if (std::string(argv[1]) == "--product-write") {
|
|
if (std::ifstream(argv[2], std::ios::binary))
|
|
throw std::runtime_error("supplemental snapshot already exists");
|
|
std::ofstream output(argv[2], std::ios::binary);
|
|
output << actual;
|
|
output.close();
|
|
if (!output) throw std::runtime_error("supplemental snapshot write failure");
|
|
} else {
|
|
std::ifstream input(argv[2], std::ios::binary);
|
|
const std::string expected((std::istreambuf_iterator<char>(input)), {});
|
|
if (expected != actual)
|
|
throw std::runtime_error("supplemental product snapshot mismatch");
|
|
}
|
|
std::cout << argv[1] << " passed; 3072 product cases and paired MKL outer "
|
|
"checks, 651 channels, input aliases, rounding/FTZ/DAZ/sticky\n";
|
|
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;
|
|
}
|
|
}
|