From 8c7c41729eb9eaa904f6739b54d34c37b1bd555f Mon Sep 17 00:00:00 2001 From: "KOKO\\Mimi" Date: Wed, 14 Oct 2026 16:39:17 +0900 Subject: [PATCH] perf(assembly): use MKL entry products with ordered load diagnostics --- docs/calculation-performance/mkl-coverage.md | 9 +++- src/fesa/assembly/load_assembler.cpp | 27 +++++++++++- tests/unit/assembly/load_assembler_test.cpp | 44 ++++++++++++++++++++ 3 files changed, 76 insertions(+), 4 deletions(-) diff --git a/docs/calculation-performance/mkl-coverage.md b/docs/calculation-performance/mkl-coverage.md index cebfa28..f02cccc 100644 --- a/docs/calculation-performance/mkl-coverage.md +++ b/docs/calculation-performance/mkl-coverage.md @@ -245,9 +245,14 @@ original/optimized 각400000 products를3회 대조했다. Original은 CSR consumer 전수 보강 검색은 [csr-consumer-audit](../../.harness/performance/t2/csr-consumer-audit.txt)에 있다. -`assembly/load_assembler.cpp::EffectiveFreeRhs`의 Kfc*dc는 root owner가 T2 통합 뒤 +`assembly/load_assembler.cpp::EffectiveFreeRhs`의 Kfc*dc는 공통 조립 담당자가 entrywise MKL diagonal products와 기존 개별 product/ordered row-sum finite gate로 -연결한다. 이 공유 파일은 T2가 수정하지 않았다. 최종 production 전수 완료는 +연결했다. O(nnz) 입력 벡터를 사용하며 원래 CSR 순서의 상쇄와 첫 오류 진단을 유지한다. +변경 전 LoadAssembly 13개, 변경 후 관련 조립·복구·선형 정적 검사 41개가 native 0으로 +통과했다. [실행 기록](../../.harness/performance/shared-assembly/after-native.json)과 +[검사 로그](../../.harness/performance/shared-assembly/after-checks.txt)를 보존한다. +독립 정적 검토는 compact band k=0 layout과 row 진단 순서 보존을 확인했다. +이 공유 파일은 T2가 수정하지 않았다. 최종 production 전수 완료는 이 인계와 T3/T4 일반 연산 적용까지 확인해야 한다. 최종 source/test hash·production 검토 이후 source 변경 없음·874개 검사 discovery native0은 diff --git a/src/fesa/assembly/load_assembler.cpp b/src/fesa/assembly/load_assembler.cpp index 7ad0ed6..caa0563 100644 --- a/src/fesa/assembly/load_assembler.cpp +++ b/src/fesa/assembly/load_assembler.cpp @@ -11,6 +11,7 @@ #include "fesa/constraints/essential_constraint_policy.h" #include "fesa/loads/load.h" #include "fesa/model/source_target_resolver.h" +#include "math/dense_blas_internal.h" namespace fesa { namespace { @@ -279,13 +280,35 @@ Result LoadAssembler::EffectiveFreeRhs(const Vector& full_load, return Result::Failure(prescribed_status); } + Vector entry_coefficients{kfc.Values().size()}; + Vector entry_prescribed{kfc.Values().size()}; + for (std::size_t position = 0U; position < kfc.Values().size(); ++position) { + entry_coefficients[position] = kfc.Values()[position]; + entry_prescribed[position] = + prescribed_values[kfc.ColumnIndices()[position]]; + } + Vector entry_products{0U}; + try { + entry_products = dense_blas_internal::MultiplyDiagonal(entry_coefficients, + entry_prescribed); + } catch (const std::length_error&) { + return Result::Failure(LoadFailure( + "load-backend-dimension-overflow", location, "LOAD_ASSEMBLER", "Kfc", + "Prescribed displacement products exceed the backend integer range.")); + } catch (const std::invalid_argument&) { + return Result::Failure(LoadFailure( + "invalid-load-product", location, "LOAD_ASSEMBLER", "Kfc", + "Prescribed displacement product inputs must be finite and compatible.")); + } + + // MKL computes independent entries; ordered CSR reduction below retains + // cancellation and the first product/row-sum overflow diagnostic. Vector correction{kfc.Rows()}; for (std::size_t row = 0U; row < kfc.Rows(); ++row) { double sum = 0.0; for (std::size_t position = kfc.RowOffsets()[row]; position < kfc.RowOffsets()[row + 1U]; ++position) { - const double product = kfc.Values()[position] * - prescribed_values[kfc.ColumnIndices()[position]]; + const double product = entry_products[position]; if (!std::isfinite(product)) { return Result::Failure(LoadFailure( "nonfinite-load-accumulation", location, "LOAD_ASSEMBLER", diff --git a/tests/unit/assembly/load_assembler_test.cpp b/tests/unit/assembly/load_assembler_test.cpp index b8f5ad8..e8f9b5a 100644 --- a/tests/unit/assembly/load_assembler_test.cpp +++ b/tests/unit/assembly/load_assembler_test.cpp @@ -481,3 +481,47 @@ TEST(LoadAssembly, ZeroLoadsRemainZero) { ASSERT_TRUE(constrained_rhs.HasValue()); EXPECT_EQ(constrained_rhs.Value().Size(), 0U); } + +TEST(LoadAssembly, PreservesOrderedCancellationInPrescribedCorrection) { + const std::filesystem::path source{"models/load-assembly.inp"}; + auto fixture = MakeFixture( + 1U, {}, + {{"10", 1, 1, 1.0, {source, 21U}}, + {"10", 3, 3, 1.0, {source, 22U}}, + {"10", 5, 5, 1.0, {source, 23U}}}, + {}); + const double large = 72057594037927936.0; // 2^56, exactly representable. + const auto kfc = MakeDenseSparse( + 3U, 3U, {large, 1.0, -large, large, -large, 1.0, 0.0, 0.0, 0.0}); + + const auto rhs = fesa::LoadAssembler::EffectiveFreeRhs( + fesa::Vector{6U}, kfc, fixture.dofs->PrescribedValues(), *fixture.dofs); + + ASSERT_TRUE(rhs.HasValue()); + ASSERT_EQ(rhs.Value().Size(), 3U); + EXPECT_DOUBLE_EQ(rhs.Value()[0U], 0.0); + EXPECT_DOUBLE_EQ(rhs.Value()[1U], -1.0); + EXPECT_DOUBLE_EQ(rhs.Value()[2U], 0.0); +} + +TEST(LoadAssembly, SelectsFirstOrderedOverflowDiagnostic) { + const std::filesystem::path source{"models/load-assembly.inp"}; + auto fixture = MakeFixture( + 1U, {}, + {{"10", 1, 1, 1.0, {source, 21U}}, + {"10", 3, 3, 1.0, {source, 22U}}, + {"10", 5, 5, 2.0, {source, 23U}}}, + {}); + const double maximum = (std::numeric_limits::max)(); + const auto kfc = MakeDenseSparse( + 3U, 3U, {maximum, maximum, 0.0, 0.0, 0.0, maximum, 0.0, 0.0, 0.0}); + + const auto rhs = fesa::LoadAssembler::EffectiveFreeRhs( + fesa::Vector{6U}, kfc, fixture.dofs->PrescribedValues(), *fixture.dofs); + + ExpectFailureCode(rhs, "nonfinite-load-accumulation"); + ASSERT_EQ(rhs.GetStatus().Diagnostics().size(), 1U); + EXPECT_EQ(rhs.GetStatus().Diagnostics()[0U].entity_identity, "0"); + EXPECT_NE(rhs.GetStatus().Diagnostics()[0U].message.find("row sum"), + std::string::npos); +}