fix(math): preserve certified sums and complete MKL algebra

This commit is contained in:
KOKO\Mimi
2026-10-14 15:40:52 +09:00
parent 59c8e93b07
commit 590a93793f
9 changed files with 236 additions and 95 deletions
+59 -52
View File
@@ -122,12 +122,11 @@ Result<NodalLift> MakeLift(const Matrix& mass,
.Multiply(basis);
double scale = 0.0;
double basis_error = 0.0;
const auto basis_gram =
dense_blas_internal::MultiplyTransposeLeft(basis, basis);
for (std::size_t i = 0; i < positive; ++i) {
for (std::size_t j = 0; j < positive; ++j) {
double gram = 0.0;
for (std::size_t a = 0; a < 6U; ++a) {
gram += basis(a, i) * basis(a, j);
}
const double gram = basis_gram(i, j);
basis_error = std::hypot(basis_error, gram - (i == j ? 1.0 : 0.0));
scale = (std::max)(scale, std::abs(mp(i, j)));
}
@@ -137,32 +136,34 @@ Result<NodalLift> MakeLift(const Matrix& mass,
return Result<NodalLift>::Failure(Failure("invalid-eigen-mass-space", node,
"Invalid positive mass basis."));
}
Matrix normalized_mp(positive, positive);
for (std::size_t i = 0; i < positive; ++i)
for (std::size_t j = 0; j < positive; ++j) {
normalized_mp(i, j) = mp(i, j) / scale;
if (!std::isfinite(normalized_mp(i, j)))
return Result<NodalLift>::Failure(
Failure("eigen-mass-conditioning", node,
"A positive-space mass pivot is not resolvable."));
}
Matrix lower(positive, positive);
for (std::size_t i = 0; i < positive; ++i) {
for (std::size_t j = 0; j <= i; ++j) {
double value = mp(i, j) / scale;
for (std::size_t k = 0; k < j; ++k) value -= lower(i, k) * lower(j, k);
if (i == j) {
if (!(value > 0.0) || !std::isfinite(value)) {
return Result<NodalLift>::Failure(
Failure("eigen-mass-conditioning", node,
"A positive-space mass pivot is not resolvable."));
}
lower(i, j) = std::sqrt(value);
} else {
lower(i, j) = value / lower(j, j);
}
}
}
Matrix inverse_lower(positive, positive);
for (std::size_t j = 0; j < positive; ++j) {
for (std::size_t i = 0; i < positive; ++i) {
double value = i == j ? 1.0 : 0.0;
for (std::size_t k = 0; k < i; ++k)
value -= lower(i, k) * inverse_lower(k, j);
inverse_lower(i, j) = value / lower(i, i);
}
if (!dense_blas_internal::CholeskyLowerAndInverse(normalized_mp, lower,
inverse_lower)) {
return Result<NodalLift>::Failure(
Failure("eigen-mass-conditioning", node,
"A positive-space mass pivot is not resolvable."));
}
Matrix transposed_lower(positive, positive);
Matrix scaled_inverse_transpose(positive, positive);
const double root_scale = std::sqrt(scale);
for (std::size_t i = 0; i < positive; ++i)
for (std::size_t j = 0; j < positive; ++j) {
transposed_lower(i, j) = lower(j, i);
scaled_inverse_transpose(i, j) = inverse_lower(j, i) / root_scale;
}
const auto inverse_mass = dense_blas_internal::MultiplyTransposeLeft(
inverse_lower, inverse_lower);
const auto rebuilt_mass = lower.Multiply(transposed_lower);
double norm_mp = 0.0;
double norm_inverse = 0.0;
double reconstruction = 0.0;
@@ -172,12 +173,8 @@ Result<NodalLift> MakeLift(const Matrix& mass,
double row_mp = 0.0;
double row_inverse = 0.0;
for (std::size_t j = 0; j < positive; ++j) {
double inverse = 0.0;
double rebuilt = 0.0;
for (std::size_t k = 0; k < positive; ++k) {
inverse += inverse_lower(k, i) * inverse_lower(k, j);
rebuilt += lower(i, k) * lower(j, k);
}
const double inverse = inverse_mass(i, j);
const double rebuilt = rebuilt_mass(i, j);
row_inverse += std::abs(inverse);
row_mp += std::abs(mp(i, j) / scale);
reconstruction = std::hypot(reconstruction, rebuilt - mp(i, j) / scale);
@@ -195,13 +192,9 @@ Result<NodalLift> MakeLift(const Matrix& mass,
"eigen-mass-conditioning", node,
"Nodal mass fails rcond_inf >= 2^-32 or Cholesky reconstruction."));
}
const double root_scale = std::sqrt(scale);
result.scaled_lift = basis.Multiply(scaled_inverse_transpose);
for (std::size_t c = 0; c < 6U; ++c) {
for (std::size_t j = 0; j < positive; ++j) {
for (std::size_t k = 0; k < positive; ++k) {
result.scaled_lift(c, j) +=
basis(c, k) * inverse_lower(j, k) / root_scale;
}
if (!std::isfinite(result.scaled_lift(c, j))) {
return Result<NodalLift>::Failure(Failure(
"eigen-mass-conditioning", node, "Nodal whitening is nonfinite."));
@@ -280,19 +273,23 @@ Result<EigenReduction> BuildEigenReduction(
free_count += lift.Value().record.free_count;
null_count += lift.Value().record.null_count;
const auto& z = lift.Value().null_vector;
Vector null_coordinate{6U};
Matrix null_column{6U, 1U};
for (std::size_t a = 0; a < 6U; ++a) {
null_coordinate[a] = z[a];
null_column(a, 0U) = z[a];
}
const auto mass_null_action = scaled.Multiply(null_coordinate);
const auto positive_null = dense_blas_internal::MultiplyTransposeLeft(
lift.Value().positive_basis, null_column);
for (std::size_t a = 0; a < 6U; ++a) {
null_norm = std::hypot(null_norm, z[a]);
expected_kz_norm = std::hypot(expected_kz_norm, drilling[node] * z[a]);
if (mask[6U * node + a] != 0U) continue;
double action = 0.0;
for (std::size_t b = 0; b < 6U; ++b) action += scaled(a, b) * z[b];
mass_null_error = std::hypot(mass_null_error, action);
mass_null_error = std::hypot(mass_null_error, mass_null_action[a]);
}
for (std::size_t j = 0; j < lift.Value().positive_basis.Columns(); ++j) {
double dot = 0.0;
for (std::size_t a = 0; a < 6U; ++a)
dot += lift.Value().positive_basis(a, j) * z[a];
pz_norm = std::hypot(pz_norm, dot);
pz_norm = std::hypot(pz_norm, positive_null(j, 0U));
}
lifts.push_back(std::move(lift.Value()));
}
@@ -318,10 +315,6 @@ Result<EigenReduction> BuildEigenReduction(
for (std::size_t a = 0; a < 6U; ++a) {
const auto row = node * 6U + a;
if (mask[row] != 0U) continue;
std::map<std::size_t, double> null_action;
if (lifts[node].record.null_count != 0U) {
null_action[node] = -drilling[node] * lifts[node].null_vector[a];
}
for (std::size_t pos = offsets[row]; pos < offsets[row + 1U]; ++pos) {
const auto col = columns[pos];
if (mask[col] != 0U) continue;
@@ -334,14 +327,28 @@ Result<EigenReduction> BuildEigenReduction(
"Scaled stiffness is nonfinite."));
}
stiffness_norm = std::hypot(stiffness_norm, value);
null_action[other] += value * lifts[other].null_vector[b];
auto block = blocks.try_emplace(other, 6U, 6U).first;
block->second(a, b) = value;
}
for (const auto& item : null_action)
kz_error = std::hypot(kz_error, item.second);
}
const auto& left = lifts[node];
std::map<std::size_t, Vector> null_actions;
for (const auto& entry : blocks) {
Vector null_coordinate{6U};
for (std::size_t b = 0; b < 6U; ++b)
null_coordinate[b] = lifts[entry.first].null_vector[b];
null_actions.emplace(entry.first, entry.second.Multiply(null_coordinate));
}
if (left.record.null_count != 0U) {
auto& local = null_actions.try_emplace(node, 6U).first->second;
for (std::size_t a = 0; a < 6U; ++a)
local[a] -= drilling[node] * left.null_vector[a];
}
for (std::size_t a = 0; a < 6U; ++a) {
if (mask[node * 6U + a] != 0U) continue;
for (const auto& action : null_actions)
kz_error = std::hypot(kz_error, action.second[a]);
}
std::map<std::size_t, Matrix> projected_blocks;
for (const auto& entry : blocks) {
projected_blocks.emplace(
+25 -30
View File
@@ -8,6 +8,7 @@
#include <utility>
#include "thermal_transient_bounds.h"
#include "math/dense_blas_internal.h"
namespace fesa::thermal_transient_internal {
namespace {
@@ -117,14 +118,9 @@ Result<Equations> Build(const ThermalSystem& s, const ThermalPartition& p,
constrained[p.constrained[i]] = i;
SparsePattern pattern{{0}, {}};
std::vector<CooContribution> coo;
SparsePattern rhs_pattern{{0U}, {}};
std::vector<CooContribution> capacity_rhs, conductivity_rhs;
Vector capacity_input = old;
Vector prescribed_full{m};
std::vector<bool> capacity_column_used(m, false);
for (std::size_t i = 0; i < p.constrained.size(); ++i) {
prescribed_full[p.constrained[i]] = p.prescribed[i];
}
const auto entries = s.c.Values().size();
Vector capacity_diagonal{entries}, conductivity_diagonal{entries};
Vector capacity_input{entries}, conductivity_input{entries};
Vector b{n};
std::vector<I> exact_a, exact_b(n);
for (std::size_t f = 0; f < n; ++f) {
@@ -135,43 +131,39 @@ Result<Equations> Build(const ThermalSystem& s, const ThermalPartition& p,
const auto col = s.c.ColumnIndices()[j];
const double c = s.c.Values()[j], k = s.k.Values()[j];
const I cd = Div(I{c}, I{delta});
rhs_pattern.column_indices.push_back(col);
capacity_column_used[col] = true;
capacity_rhs.push_back({f, col, c / delta, 0U, j});
conductivity_rhs.push_back({f, col, k, 0U, j});
capacity_diagonal[j] = c / delta;
if (free[col] != n) {
capacity_input[j] = old[col];
coo.push_back({f, free[col], c / delta + k, 0, coo.size()});
pattern.column_indices.push_back(free[col]);
exact_a.push_back(Add(cd, I{k}));
exact_b[f] = Add(exact_b[f], Mul(cd, I{old[col]}));
} else {
const double g = p.prescribed[constrained[col]];
capacity_input[j] = old[col] - g;
conductivity_diagonal[j] = k;
conductivity_input[j] = g;
// Preserve raw old constrained capacity; never project frame zero.
exact_b[f] = Add(exact_b[f],
Sub(Mul(cd, Sub(I{old[col]}, I{g})), Mul(I{k}, I{g})));
}
}
pattern.row_offsets.push_back(coo.size());
rhs_pattern.row_offsets.push_back(capacity_rhs.size());
}
// Do not create overflow in an absent constrained column: the original
// free-row RHS never evaluated that subtraction. Referenced structural
// zeros still evaluate it and reject nonfinite intermediate values.
for (std::size_t i = 0; i < p.constrained.size(); ++i) {
const auto column = p.constrained[i];
if (capacity_column_used[column]) capacity_input[column] -= p.prescribed[i];
const auto capacity_action = dense_blas_internal::MultiplyDiagonal(
capacity_diagonal, capacity_input);
const auto conductivity_action = dense_blas_internal::MultiplyDiagonal(
conductivity_diagonal, conductivity_input);
for (std::size_t f = 0; f < n; ++f) {
const auto row = p.free[f];
for (auto j = s.c.RowOffsets()[row]; j < s.c.RowOffsets()[row + 1]; ++j) {
const auto col = s.c.ColumnIndices()[j];
// Preserve the original per-entry subtraction before the row sum.
// Separately summing C*delta and K*g loses small stable RHS terms.
b[f] += free[col] != n ? capacity_action[j]
: capacity_action[j] - conductivity_action[j];
}
}
if (!Finite(capacity_input))
return Result<Equations>::Failure(Failure("Nonfinite backward Euler RHS."));
auto c_rhs = SparseMatrix::FromCoo(n, m, std::move(capacity_rhs), rhs_pattern);
auto k_rhs = SparseMatrix::FromCoo(n, m, std::move(conductivity_rhs),
rhs_pattern);
if (!c_rhs.HasValue() || !k_rhs.HasValue())
return Result<Equations>::Failure(Failure("Nonfinite backward Euler RHS."));
const auto capacity_action = c_rhs.Value().Multiply(capacity_input);
const auto conductivity_action = k_rhs.Value().Multiply(prescribed_full);
for (std::size_t f = 0; f < n; ++f)
b[f] += capacity_action[f] - conductivity_action[f];
auto a = SparseMatrix::FromCoo(n, n, std::move(coo), pattern);
if (!a.HasValue()) return Result<Equations>::Failure(a.GetStatus());
if (!Finite(b))
@@ -493,6 +485,9 @@ Result<ThermalStepCandidate> SolveThermalIncrement(
status = Check(s, p, old, delta, equations.Value(), candidate);
if (!status.IsOk()) return Result<ThermalStepCandidate>::Failure(status);
return Result<ThermalStepCandidate>::Success(std::move(candidate));
} catch (const std::invalid_argument&) {
return Result<ThermalStepCandidate>::Failure(
Failure("Nonfinite backward Euler RHS."));
} catch (const std::length_error&) {
return Result<ThermalStepCandidate>::Failure(
Failure("Thermal sparse action exceeds the backend integer range."));