From 664d3ff2a1848ee4bba421a576d02dd7c006ae13 Mon Sep 17 00:00:00 2001 From: "KOKO\\Mimi" Date: Sun, 9 Aug 2026 19:27:47 +0900 Subject: [PATCH] feat(linear-static-3d-euler-beam): step 18 - sparse-assembly --- .harness/step18-discovery.json | 1709 +++++++++++++++++ ...tic-3d-euler-beam-implementation-report.md | 63 + include/fesa/assembly/sparse_assembler.hpp | 20 + include/fesa/math/sparse_matrix.hpp | 53 + src/fesa/CMakeLists.txt | 2 + src/fesa/assembly/sparse_assembler.cpp | 183 ++ src/fesa/math/sparse_matrix.cpp | 239 +++ tests/CMakeLists.txt | 2 + tests/unit/assembly/sparse_assembler_test.cpp | 158 ++ tests/unit/math/sparse_matrix_test.cpp | 149 ++ 10 files changed, 2578 insertions(+) create mode 100644 .harness/step18-discovery.json create mode 100644 include/fesa/assembly/sparse_assembler.hpp create mode 100644 include/fesa/math/sparse_matrix.hpp create mode 100644 src/fesa/assembly/sparse_assembler.cpp create mode 100644 src/fesa/math/sparse_matrix.cpp create mode 100644 tests/unit/assembly/sparse_assembler_test.cpp create mode 100644 tests/unit/math/sparse_matrix_test.cpp diff --git a/.harness/step18-discovery.json b/.harness/step18-discovery.json new file mode 100644 index 0000000..9d56c2c --- /dev/null +++ b/.harness/step18-discovery.json @@ -0,0 +1,1709 @@ +{ + "backtraceGraph" : + { + "commands" : [], + "files" : [], + "nodes" : [] + }, + "kind" : "ctestInfo", + "tests" : + [ + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=BuildInfo.VersionIsStableAndNonEmpty", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "BuildInfo.VersionIsStableAndNonEmpty", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\build_info_test.cpp:10" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=BuildInfo.PublicHeaderHasNoBackendDependency", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "BuildInfo.PublicHeaderHasNoBackendDependency", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\build_info_test.cpp:20" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=AnalysisModel.ClassifiesActiveEntitiesInStableOrder", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "AnalysisModel.ClassifiesActiveEntitiesInStableOrder", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\analysis\\analysis_model_test.cpp:59" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=AnalysisModel.ReferencesWithoutCopyingOrMutatingDomain", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "AnalysisModel.ReferencesWithoutCopyingOrMutatingDomain", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\analysis\\analysis_model_test.cpp:84" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=AnalysisModel.RejectsMissingOrMultipleStep", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "AnalysisModel.RejectsMissingOrMultipleStep", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\analysis\\analysis_model_test.cpp:116" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=AnalysisState.AllocatesOnlyV0FullVectors", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "AnalysisState.AllocatesOnlyV0FullVectors", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\analysis\\analysis_state_test.cpp:84" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=AnalysisState.CopiesOrMovesWithoutAliasing", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "AnalysisState.CopiesOrMovesWithoutAliasing", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\analysis\\analysis_state_test.cpp:123" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=AnalysisState.PreservesStepFrameAndStableRowOrder", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "AnalysisState.PreservesStepFrameAndStableRowOrder", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\results\\result_records_test.cpp:30" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=ParallelFor.ZeroOneManyExecuteExactlyOnce", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "ParallelFor.ZeroOneManyExecuteExactlyOnce", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\assembly\\parallel_for_test.cpp:27" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=ParallelFor.SerialAndTbbProduceStableIndexedOutput", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "ParallelFor.SerialAndTbbProduceStableIndexedOutput", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\assembly\\parallel_for_test.cpp:53" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=ParallelFor.PropagatesBodyExceptionByContract", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "ParallelFor.PropagatesBodyExceptionByContract", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\assembly\\parallel_for_test.cpp:93" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=SparseAssembly.SerialTbbAndRepeatedRunsAreByteIdentical", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "SparseAssembly.SerialTbbAndRepeatedRunsAreByteIdentical", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\assembly\\sparse_assembler_test.cpp:101" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=SparseAssembly.ValidatesKnownCsrAndMultiply", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "SparseAssembly.ValidatesKnownCsrAndMultiply", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\math\\sparse_matrix_test.cpp:24" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=SparseAssembly.ReducesDuplicatesInFixedTupleOrder", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "SparseAssembly.ReducesDuplicatesInFixedTupleOrder", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\math\\sparse_matrix_test.cpp:61" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=SparseAssembly.RejectsInvalidIndexPatternAndShape", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "SparseAssembly.RejectsInvalidIndexPatternAndShape", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\math\\sparse_matrix_test.cpp:83" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=SparseAssembly.PreservesExpectedStructuralZeros", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "SparseAssembly.PreservesExpectedStructuralZeros", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\math\\sparse_matrix_test.cpp:127" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=CoreDiagnostics.DiagnosticsSortDeterministically", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "CoreDiagnostics.DiagnosticsSortDeterministically", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\core\\diagnostic_test.cpp:30" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=CoreDiagnostics.SourceIdentityPreservesRawIdentity", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "CoreDiagnostics.SourceIdentityPreservesRawIdentity", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\core\\source_identity_test.cpp:9" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=CoreDiagnostics.ResultEnforcesValueErrorExclusivity", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "CoreDiagnostics.ResultEnforcesValueErrorExclusivity", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\core\\status_test.cpp:25" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=EulerBeam3D.HermiteAndBMatrixMatchReviewedSigns", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "EulerBeam3D.HermiteAndBMatrixMatchReviewedSigns", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\elements\\euler_beam_3d_test.cpp:513" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=EulerBeam3D.TwoPointGaussMatchesClosedStiffness", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "EulerBeam3D.TwoPointGaussMatchesClosedStiffness", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\elements\\euler_beam_3d_test.cpp:584" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=EulerBeam3D.HasSixRigidModesRankSixAndPositiveDeformationEnergy", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "EulerBeam3D.HasSixRigidModesRankSixAndPositiveDeformationEnergy", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\elements\\euler_beam_3d_test.cpp:607" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=EulerBeam3D.RotatedTransformPreservesWorkAndEnergy", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "EulerBeam3D.RotatedTransformPreservesWorkAndEnergy", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\elements\\euler_beam_3d_test.cpp:667" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=EulerBeam3D.ConstantLineLoadMatchesAllSignedComponents", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "EulerBeam3D.ConstantLineLoadMatchesAllSignedComponents", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\elements\\euler_beam_3d_test.cpp:742" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=EulerBeam3D.AnalyticalAxialTorsionAndTwoPlaneBendingRecover", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "EulerBeam3D.AnalyticalAxialTorsionAndTwoPlaneBendingRecover", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\elements\\euler_beam_3d_test.cpp:810" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=EulerBeam3D.RejectsInvalidGeometryAndProperties", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "EulerBeam3D.RejectsInvalidGeometryAndProperties", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\elements\\euler_beam_3d_test.cpp:926" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=EulerBeam3D.RecoversSectionPointAndDefaultCentroidS11", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "EulerBeam3D.RecoversSectionPointAndDefaultCentroidS11", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\elements\\euler_beam_3d_test.cpp:1050" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=EulerBeam3D.ReproducesConstantStrainTwistAndCurvaturePatches", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "EulerBeam3D.ReproducesConstantStrainTwistAndCurvaturePatches", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\elements\\euler_beam_3d_test.cpp:1102" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=EulerBeam3D.OnePointNegativeControlHasRankFour", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "EulerBeam3D.OnePointNegativeControlHasRankFour", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\elements\\euler_beam_3d_test.cpp:1163" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=DofManager.NumbersSixDofsAndFreeEquationsStably", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "DofManager.NumbersSixDofsAndFreeEquationsStably", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\fem\\dof_manager_test.cpp:72" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=DofManager.ExpandsAndValidatesPrescribedValues", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "DofManager.ExpandsAndValidatesPrescribedValues", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\fem\\dof_manager_test.cpp:96" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=DofManager.BuildsTwelveDofScatterAndSortedUniquePattern", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "DofManager.BuildsTwelveDofScatterAndSortedUniquePattern", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\fem\\dof_manager_test.cpp:126" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=DofManager.ReconstructsFullReducedRoundTrip", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "DofManager.ReconstructsFullReducedRoundTrip", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\fem\\dof_manager_test.cpp:164" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=DenseMath.RowMajorMatrixMatchesKnownGemvGemm", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "DenseMath.RowMajorMatrixMatchesKnownGemvGemm", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\math\\matrix_test.cpp:12" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=DenseMath.VectorOwnsAndChecksContiguousStorage", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "DenseMath.VectorOwnsAndChecksContiguousStorage", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\math\\vector_test.cpp:12" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=InpDomainMapping.MapsEverySupportedKeywordAndLegacyDeck", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "InpDomainMapping.MapsEverySupportedKeywordAndLegacyDeck", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\io\\abaqus\\domain_mapper_test.cpp:198" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=InpDomainMapping.ExpandsSetsAndMultipleIdentityInstancesDeterministically", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "InpDomainMapping.ExpandsSetsAndMultipleIdentityInstancesDeterministically", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\io\\abaqus\\domain_mapper_test.cpp:271" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=InpDomainMapping.NoOpAllowlistWarnsWithoutSemanticEffect", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "InpDomainMapping.NoOpAllowlistWarnsWithoutSemanticEffect", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\io\\abaqus\\domain_mapper_test.cpp:390" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=InpDomainMapping.RejectsUnsupportedAndInvalidPortfolio", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "InpDomainMapping.RejectsUnsupportedAndInvalidPortfolio", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\io\\abaqus\\domain_mapper_test.cpp:425" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=InpDomainMapping.RejectsDloadWithoutDistributedLoadObject", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "InpDomainMapping.RejectsDloadWithoutDistributedLoadObject", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\io\\abaqus\\domain_mapper_test.cpp:613" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=InpSyntax.RejectsMalformedOrOrphanData", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "InpSyntax.RejectsMalformedOrOrphanData", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\io\\abaqus\\input_reader_test.cpp:59" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=InpSyntax.ReadsLegacyCantileverWithoutMutation", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "InpSyntax.ReadsLegacyCantileverWithoutMutation", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\io\\abaqus\\input_reader_test.cpp:99" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=InpSyntax.CanonicalizesKeywordAndParameterNamesOnly", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "InpSyntax.CanonicalizesKeywordAndParameterNamesOnly", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\io\\abaqus\\input_syntax_test.cpp:40" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=InpSyntax.PreservesDataAndSourceLocations", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "InpSyntax.PreservesDataAndSourceLocations", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\io\\abaqus\\input_syntax_test.cpp:66" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=DomainModel.ImmutableOwnershipPreservesStableOrder", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "DomainModel.ImmutableOwnershipPreservesStableOrder", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\model\\domain_test.cpp:73" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=DomainModel.MultipleIdentityInstancesDoNotMerge", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "DomainModel.MultipleIdentityInstancesDoNotMerge", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\model\\domain_test.cpp:129" + } + ] + }, + { + "command" : + [ + "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests/Debug/fesa_unit_tests.exe", + "--gtest_filter=DomainModel.SourceAndInternalIdentityRemainDistinct", + "--gtest_also_run_disabled_tests" + ], + "config" : "Debug", + "name" : "DomainModel.SourceAndInternalIdentityRemainDistinct", + "properties" : + [ + { + "name" : "SKIP_REGULAR_EXPRESSION", + "value" : + [ + "\\[ SKIPPED \\]" + ] + }, + { + "name" : "LABELS", + "value" : + [ + "linear-static-3d-euler-beam", + "unit" + ] + }, + { + "name" : "WORKING_DIRECTORY", + "value" : "C:/Users/baram/orca/workspaces/FESADev/LinearStatic/.harness/build/tests" + }, + { + "name" : "DEF_SOURCE_LINE", + "value" : "C:\\Users\\baram\\orca\\workspaces\\FESADev\\LinearStatic\\tests\\unit\\model\\model_types_test.cpp:32" + } + ] + } + ], + "version" : + { + "major" : 1, + "minor" : 0 + } +} diff --git a/docs/implementation-plans/linear-static-3d-euler-beam-implementation-report.md b/docs/implementation-plans/linear-static-3d-euler-beam-implementation-report.md index c59c65f..ed25db3 100644 --- a/docs/implementation-plans/linear-static-3d-euler-beam-implementation-report.md +++ b/docs/implementation-plans/linear-static-3d-euler-beam-implementation-report.md @@ -716,3 +716,66 @@ | REVIEW1-VERIFY-discovery | `ctest --test-dir .harness/build -C Debug --show-only=json-v1` | 0 | 42 tests discovered, including the unchanged three exact ParallelFor names | | REVIEW1-VERIFY-full | `ctest --test-dir .harness/build -C Debug --output-on-failure` | 0 | full accumulated suite passed 42/42 | | REVIEW1-VERIFY-scans | public TBB leak, production scope, atomic reliability, reference, and diff/whitespace scans | 0 | public leaks 0; forbidden production scope 0; atomic vectors 5; relaxed duplicate counters 4; legacy non-atomic visit/output paths 0; reference unchanged; diff clean | + +## Step 18 — sparse-assembly + +- task_id: `TASK-18` +- status: `completed` +- changed_files: `include/fesa/math/sparse_matrix.hpp`, + `src/fesa/math/sparse_matrix.cpp`, + `include/fesa/assembly/sparse_assembler.hpp`, + `src/fesa/assembly/sparse_assembler.cpp`, + `tests/unit/math/sparse_matrix_test.cpp`, + `tests/unit/assembly/sparse_assembler_test.cpp`, `src/fesa/CMakeLists.txt`, + `tests/CMakeLists.txt`, + `docs/implementation-plans/linear-static-3d-euler-beam-implementation-report.md`, + `phases/linear-static-3d-euler-beam/index.json`, + `.superpowers/sdd/linear-static-3d-euler-beam/task-18-report.md` +- requirement_ids: `FESA-REQ-LS3DEB-024`, `FESA-REQ-LS3DEB-025`, + `FESA-REQ-LS3DEB-034`, `FESA-REQ-LS3DEB-035` +- test_ids: `T18-SPARSE-001`, `T18-SPARSE-002`, `T18-SPARSE-003`, + `T18-SPARSE-004`, `T18-SPARSE-005` + +| stage | exact command | exit_code | expected_or_observed_result | evidence_tail | +| --- | --- | ---: | --- | --- | +| RED-build | `cmake --build .harness/build --config Debug --target fesa_tests` | 1 | Exactly five planned tests were registered before production and both sparse public APIs were absent | CMake regenerated, then MSVC C1083 reported missing `fesa/assembly/sparse_assembler.hpp` and `fesa/math/sparse_matrix.hpp` from the two new test files | +| GREEN-build | `cmake --build .harness/build --config Debug --target fesa_tests` | 0 | Minimum CSR owner, deterministic assembler, five tests, solver library, and unit executable compile and link | `sparse_assembler.cpp`, `sparse_matrix.cpp`, both tests, `fesa_solver.lib`, and `fesa_unit_tests.exe` built without a FESA warning under `/W4 /WX` | +| GREEN-test | `ctest --test-dir .harness/build -C Debug -R SparseAssembly --output-on-failure` | 0 | CSR validation/reduction/zero policy and serial/TBB deterministic beam assembly pass | 5/5 exact `SparseAssembly` tests passed | +| VERIFY-configure | `cmake -S . -B .harness/build -A x64 -DFESA_GTEST_SOURCE_DIR=C:/git/googletest "-DMKL_DIR=C:/Program Files (x86)/Intel/oneAPI/mkl/2026.1/lib/cmake/mkl" "-DTBB_DIR=C:/Program Files (x86)/Intel/oneAPI/tbb/2023.1/lib/cmake/tbb" "-DHDF5_DIR=C:/Program Files/HDF_Group/HDF5/2.1.1/cmake"` | 0 | Approved explicit-dependency MSVC x64 build tree generates | Windows SDK, oneMKL 2026.1, oneTBB, and HDF5 resolved; configure and generate completed | +| VERIFY-build | `cmake --build .harness/build --config Debug` | 0 | Full Debug build passes without a new FESA warning | `fesa_solver.lib` and `fesa_unit_tests.exe` built under `/W4 /WX` | +| VERIFY-targeted | `ctest --test-dir .harness/build -C Debug -R SparseAssembly --output-on-failure` | 0 | Focused Step 18 suite remains green | 5/5 exact `SparseAssembly` tests passed | +| VERIFY-discovery | `ctest --test-dir .harness/build -C Debug --show-only=json-v1` | 0 | CTest discovers the accumulated suite and all five exact SparseAssembly names | 47 tests discovered, including 5 `SparseAssembly` tests, with feature/unit labels | +| VERIFY-full | `ctest --test-dir .harness/build -C Debug --output-on-failure` | 0 | Full accumulated C++ suite has zero failures | 47/47 tests passed | +| VERIFY-contract-scans | Public backend/upward-dependency/PARDISO, assembler shared-CSR-member, dense inheritance, exact-test-count, and CMake registration scans using fail-on-match/count `rg` wrappers | 0 | Sparse storage and reduction remain behind the exact approved boundaries | public backend leaks 0; math-header upward dependencies 0; PARDISO matches 0; assembler CSR-member writes 0; dense inheritance 0; tests 5; registrations 2/2 | +| VERIFY-determinism | Exact bytes, reversed completion, serial/TBB, repeated TBB, tuple-cancellation, structural-zero, empty-row, multiply, validation, and symmetry assertions in the targeted suite | 0 | NR-O02 fixed-order reduction and NR-T11 scheduler independence are observable | one serial, one TBB, one reverse-order, and eight repeated TBB assemblies produced byte-identical row/column/value vectors; duplicate cancellation produced the tuple-ordered literal `1.0` | +| VERIFY-diff | `git diff --check` plus trailing-whitespace scan over the six new files | 0 | Tracked and untracked Step 18 files have no whitespace errors | Diff check exit 0; new-file trailing whitespace matches 0 | +| VERIFY-reference | `git diff --exit-code -- reference/`; `git status --short -- reference/` | 0 | Approved legacy reference artifacts remain read-only and unchanged | Reference diff exit 0 and reference status empty | + +- contract_checks: `SparseMatrix` is a separate owning CSR type with 0-based + row offsets and sorted-unique columns. It validates row-offset shape/range, + column range/order/uniqueness, COO dimensions, expected-pattern membership, + finite inputs, finite ordered sums, and multiply dimensions. Empty rows and + rectangular/zero structural layouts remain valid; every expected slot is + emitted even when its exact stored value is zero. +- contract_checks: COO contributions are stable-sorted by exactly + `(row,column,elementOrder,localOrder)` and duplicates are accumulated in + that sequence. The cancellation fixture distinguishes tuple order from + incoming vector order, and input permutation retains the same result. +- contract_checks: `SparseAssembler` validates model/DOF dimensions, + element references, and scatter ownership before work. It invokes the + injected `ParallelFor` once over stable active-element order; each worker + writes only its caller-indexed 144-entry row-major local buffer and failure + slot. CSR allocation and reduction occur only after the parallel call. +- contract_checks: the public headers expose no oneTBB, MKL, HDF5, or PARDISO + type. `SparseMatrix` neither includes nor inherits dense `Matrix`; the math + header forward-declares DofManager-owned `SparsePattern` to avoid a public + upward include. +- generated_evidence: `.harness/build/src/fesa/Debug/fesa_solver.lib`, + `.harness/build/tests/Debug/fesa_unit_tests.exe` +- reference_diff: unchanged; `git diff --exit-code -- reference/` exit 0 +- handoff: Step 19 can consume canonical full CSR arrays and deterministic + matrix-vector multiplication without depending on dense storage, oneTBB, + or a solver backend. Step 20 remains solely responsible for PARDISO + descriptors, indexing conversion, factorization, and substitution. +- concerns: none; no implementation, environment, backend, or upstream + contract conflict was found. diff --git a/include/fesa/assembly/sparse_assembler.hpp b/include/fesa/assembly/sparse_assembler.hpp new file mode 100644 index 0000000..fa29b4c --- /dev/null +++ b/include/fesa/assembly/sparse_assembler.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "fesa/core/status.hpp" +#include "fesa/math/sparse_matrix.hpp" + +namespace fesa { + +class AnalysisModel; +class DofManager; +class ParallelFor; + +class SparseAssembler { +public: + static Result assembleStiffness( + const AnalysisModel& model, + const DofManager& dofs, + const ParallelFor& parallelFor); +}; + +} // namespace fesa diff --git a/include/fesa/math/sparse_matrix.hpp b/include/fesa/math/sparse_matrix.hpp new file mode 100644 index 0000000..30e9ff1 --- /dev/null +++ b/include/fesa/math/sparse_matrix.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include "fesa/core/status.hpp" +#include "fesa/math/vector.hpp" + +#include +#include + +namespace fesa { + +struct SparsePattern; + +struct CooContribution { + std::size_t row; + std::size_t column; + double value; + std::size_t elementOrder; + std::size_t localOrder; +}; + +// Owns canonical 0-based CSR data independently of the dense Matrix adapter. +class SparseMatrix { +public: + static Result fromCoo( + std::size_t rows, + std::size_t columns, + std::vector contributions, + const SparsePattern& expectedPattern); + + std::size_t rows() const noexcept; + std::size_t columns() const noexcept; + const std::vector& rowOffsets() const noexcept; + const std::vector& columnIndices() const noexcept; + const std::vector& values() const noexcept; + Vector multiply(const Vector& rhs) const; + Status validate() const; + +private: + SparseMatrix( + std::size_t rows, + std::size_t columns, + std::vector rowOffsets, + std::vector columnIndices, + std::vector values); + + std::size_t rows_; + std::size_t columns_; + std::vector rowOffsets_; + std::vector columnIndices_; + std::vector values_; +}; + +} // namespace fesa diff --git a/src/fesa/CMakeLists.txt b/src/fesa/CMakeLists.txt index b8fd5b5..81b6c90 100644 --- a/src/fesa/CMakeLists.txt +++ b/src/fesa/CMakeLists.txt @@ -4,6 +4,7 @@ add_library( analysis/analysis_model.cpp analysis/analysis_state.cpp assembly/parallel_for.cpp + assembly/sparse_assembler.cpp build_info.cpp core/diagnostic.cpp core/status.cpp @@ -12,6 +13,7 @@ add_library( io/abaqus/domain_mapper.cpp io/abaqus/input_reader.cpp math/matrix.cpp + math/sparse_matrix.cpp math/vector.cpp model/domain.cpp ) diff --git a/src/fesa/assembly/sparse_assembler.cpp b/src/fesa/assembly/sparse_assembler.cpp new file mode 100644 index 0000000..6128b1f --- /dev/null +++ b/src/fesa/assembly/sparse_assembler.cpp @@ -0,0 +1,183 @@ +#include "fesa/assembly/sparse_assembler.hpp" + +#include "fesa/analysis/analysis_model.hpp" +#include "fesa/assembly/parallel_for.hpp" +#include "fesa/elements/euler_beam_3d.hpp" +#include "fesa/fem/dof_manager.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace fesa { +namespace { + +constexpr std::size_t kDofsPerNode = 6U; +constexpr std::size_t kElementDofCount = 12U; +constexpr std::size_t kContributionCount = + kElementDofCount * kElementDofCount; + +using ElementBuffer = std::array; + +Result assemblyFailure( + const std::string& code, + const SourceLocation& location, + const std::string& identity, + const std::string& message) { + return Result::failure(Status::failure( + FailureCategory::model, + {{Severity::error, + code, + location, + "*ELEMENT", + identity, + message}})); +} + +} // namespace + +Result SparseAssembler::assembleStiffness( + const AnalysisModel& model, + const DofManager& dofs, + const ParallelFor& parallelFor) { + const Domain& domain = model.domain(); + if (domain.nodes().size() > + (std::numeric_limits::max)() / kDofsPerNode || + dofs.fullDofCount() != domain.nodes().size() * kDofsPerNode) { + return assemblyFailure( + "invalid-assembly-dimensions", + {domain.sourcePath(), 0U}, + std::to_string(dofs.fullDofCount()), + "DofManager dimensions do not match the active model nodes."); + } + if (model.activeElements().size() > + (std::numeric_limits::max)() / kContributionCount) { + return assemblyFailure( + "invalid-assembly-dimensions", + {domain.sourcePath(), 0U}, + std::to_string(model.activeElements().size()), + "Element contribution storage exceeds the addressable range."); + } + + std::vector> scatters; + scatters.reserve(model.activeElements().size()); + for (const EntityIndex elementIndex : model.activeElements()) { + if (elementIndex >= domain.elements().size()) { + return assemblyFailure( + "invalid-assembly-element", + {domain.sourcePath(), 0U}, + std::to_string(elementIndex), + "Active element index is outside the Domain."); + } + const auto& element = domain.elements()[elementIndex]; + if (element.nodeIndices[0U] >= domain.nodes().size() || + element.nodeIndices[1U] >= domain.nodes().size() || + element.materialIndex >= domain.materials().size() || + element.sectionIndex >= domain.sections().size()) { + return assemblyFailure( + "invalid-assembly-element", + element.location, + element.sourceId.sourceLabelText, + "Element references an entity outside the Domain."); + } + + std::array scatter{}; + try { + scatter = dofs.elementScatter(elementIndex); + } catch (const std::out_of_range&) { + return assemblyFailure( + "invalid-assembly-scatter", + element.location, + element.sourceId.sourceLabelText, + "DofManager does not contain the active element scatter."); + } + for (std::size_t endpoint = 0U; endpoint < 2U; ++endpoint) { + for (std::size_t component = 0U; + component < kDofsPerNode; + ++component) { + const std::size_t local = endpoint * kDofsPerNode + component; + const std::size_t expected = + static_cast(element.nodeIndices[endpoint]) * + kDofsPerNode + + component; + if (scatter[local] != expected || + scatter[local] >= dofs.fullDofCount()) { + return assemblyFailure( + "invalid-assembly-scatter", + element.location, + element.sourceId.sourceLabelText, + "Element scatter does not match the active model topology."); + } + } + } + scatters.push_back(scatter); + } + + std::vector localBuffers(model.activeElements().size()); + std::vector> localFailures( + model.activeElements().size()); + parallelFor.execute( + model.activeElements().size(), + [&](const std::size_t elementOrder) { + const EntityIndex elementIndex = model.activeElements()[elementOrder]; + const auto& definition = domain.elements()[elementIndex]; + const auto beam = EulerBeam3D::create( + domain.nodes()[definition.nodeIndices[0U]], + domain.nodes()[definition.nodeIndices[1U]], + domain.sections()[definition.sectionIndex], + domain.materials()[definition.materialIndex]); + if (!beam.hasValue()) { + localFailures[elementOrder] = beam.status(); + return; + } + + const Matrix stiffness = beam.value().globalStiffness(); + auto& buffer = localBuffers[elementOrder]; + const auto& scatter = scatters[elementOrder]; + for (std::size_t localRow = 0U; + localRow < kElementDofCount; + ++localRow) { + for (std::size_t localColumn = 0U; + localColumn < kElementDofCount; + ++localColumn) { + const std::size_t localOrder = + localRow * kElementDofCount + localColumn; + buffer[localOrder] = { + scatter[localRow], + scatter[localColumn], + stiffness(localRow, localColumn), + elementOrder, + localOrder}; + } + } + }); + + for (std::size_t elementOrder = 0U; + elementOrder < localFailures.size(); + ++elementOrder) { + if (localFailures[elementOrder]) { + return Result::failure( + *localFailures[elementOrder]); + } + } + + std::vector contributions; + contributions.reserve( + localBuffers.size() * kContributionCount); + // Flatten only after all workers complete; workers never share CSR state. + for (const auto& buffer : localBuffers) { + contributions.insert( + contributions.end(), buffer.begin(), buffer.end()); + } + return SparseMatrix::fromCoo( + dofs.fullDofCount(), + dofs.fullDofCount(), + std::move(contributions), + dofs.sparsePattern()); +} + +} // namespace fesa diff --git a/src/fesa/math/sparse_matrix.cpp b/src/fesa/math/sparse_matrix.cpp new file mode 100644 index 0000000..d3fae40 --- /dev/null +++ b/src/fesa/math/sparse_matrix.cpp @@ -0,0 +1,239 @@ +#include "fesa/math/sparse_matrix.hpp" + +#include "fesa/fem/dof_manager.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace fesa { +namespace { + +Status sparseFailure( + const std::string& code, + const std::string& identity, + const std::string& message) { + return Status::failure( + FailureCategory::model, + {{Severity::error, + code, + {{}, 0U}, + "SPARSE_MATRIX", + identity, + message}}); +} + +Status validateCsr( + const std::size_t rows, + const std::size_t columns, + const std::vector& rowOffsets, + const std::vector& columnIndices, + const std::vector* const values) { + if (rows == (std::numeric_limits::max)() || + rowOffsets.size() != rows + 1U) { + return sparseFailure( + "invalid-sparse-shape", + "row-offset-count", + "CSR row offsets must contain exactly rows plus one entries."); + } + if (rowOffsets.empty() || rowOffsets.front() != 0U || + rowOffsets.back() != columnIndices.size()) { + return sparseFailure( + "invalid-sparse-pattern", + "row-offset-range", + "CSR row offsets must start at zero and end at the column count."); + } + if (values != nullptr && values->size() != columnIndices.size()) { + return sparseFailure( + "invalid-sparse-shape", + "value-count", + "CSR column and value arrays must have equal sizes."); + } + + for (std::size_t row = 0U; row < rows; ++row) { + const std::size_t begin = rowOffsets[row]; + const std::size_t end = rowOffsets[row + 1U]; + if (begin > end || end > columnIndices.size()) { + return sparseFailure( + "invalid-sparse-pattern", + std::to_string(row), + "CSR row offsets must be nondecreasing and remain in range."); + } + for (std::size_t position = begin; position < end; ++position) { + if (columnIndices[position] >= columns) { + return sparseFailure( + "invalid-sparse-index", + std::to_string(position), + "CSR column index is outside the matrix dimensions."); + } + if (position > begin && + columnIndices[position - 1U] >= columnIndices[position]) { + return sparseFailure( + "invalid-sparse-pattern", + std::to_string(row), + "CSR columns must be sorted and unique within each row."); + } + if (values != nullptr && !std::isfinite((*values)[position])) { + return sparseFailure( + "nonfinite-sparse-value", + std::to_string(position), + "CSR values must be finite."); + } + } + } + return Status::ok(); +} + +} // namespace + +Result SparseMatrix::fromCoo( + const std::size_t rows, + const std::size_t columns, + std::vector contributions, + const SparsePattern& expectedPattern) { + const Status patternStatus = validateCsr( + rows, + columns, + expectedPattern.rowOffsets, + expectedPattern.columnIndices, + nullptr); + if (!patternStatus.isOk()) { + return Result::failure(patternStatus); + } + + for (const auto& contribution : contributions) { + if (contribution.row >= rows || contribution.column >= columns) { + return Result::failure(sparseFailure( + "invalid-sparse-index", + std::to_string(contribution.row) + ":" + + std::to_string(contribution.column), + "COO contribution index is outside the matrix dimensions.")); + } + if (!std::isfinite(contribution.value)) { + return Result::failure(sparseFailure( + "nonfinite-sparse-value", + std::to_string(contribution.elementOrder) + ":" + + std::to_string(contribution.localOrder), + "COO contribution values must be finite.")); + } + } + + // The complete tuple fixes duplicate summation order independently of + // worker completion order. stable_sort also preserves exact tuple ties. + std::stable_sort( + contributions.begin(), + contributions.end(), + [](const CooContribution& left, const CooContribution& right) { + return std::tie( + left.row, + left.column, + left.elementOrder, + left.localOrder) < + std::tie( + right.row, + right.column, + right.elementOrder, + right.localOrder); + }); + + std::vector values(expectedPattern.columnIndices.size(), 0.0); + for (const auto& contribution : contributions) { + const std::size_t begin = expectedPattern.rowOffsets[contribution.row]; + const std::size_t end = expectedPattern.rowOffsets[contribution.row + 1U]; + const auto first = expectedPattern.columnIndices.begin() + begin; + const auto last = expectedPattern.columnIndices.begin() + end; + const auto found = std::lower_bound(first, last, contribution.column); + if (found == last || *found != contribution.column) { + return Result::failure(sparseFailure( + "sparse-pattern-mismatch", + std::to_string(contribution.row) + ":" + + std::to_string(contribution.column), + "COO contribution is absent from the expected sparse pattern.")); + } + + const std::size_t position = static_cast( + std::distance(expectedPattern.columnIndices.begin(), found)); + values[position] += contribution.value; + if (!std::isfinite(values[position])) { + return Result::failure(sparseFailure( + "nonfinite-sparse-value", + std::to_string(contribution.row) + ":" + + std::to_string(contribution.column), + "Ordered COO duplicate summation produced a nonfinite value.")); + } + } + + SparseMatrix matrix{ + rows, + columns, + expectedPattern.rowOffsets, + expectedPattern.columnIndices, + std::move(values)}; + const Status status = matrix.validate(); + if (!status.isOk()) { + return Result::failure(status); + } + return Result::success(std::move(matrix)); +} + +std::size_t SparseMatrix::rows() const noexcept { + return rows_; +} + +std::size_t SparseMatrix::columns() const noexcept { + return columns_; +} + +const std::vector& SparseMatrix::rowOffsets() const noexcept { + return rowOffsets_; +} + +const std::vector& SparseMatrix::columnIndices() const noexcept { + return columnIndices_; +} + +const std::vector& SparseMatrix::values() const noexcept { + return values_; +} + +Vector SparseMatrix::multiply(const Vector& rhs) const { + if (columns_ != rhs.size()) { + throw std::invalid_argument{ + "Sparse matrix-vector multiplication has incompatible dimensions."}; + } + + Vector result{rows_}; + for (std::size_t row = 0U; row < rows_; ++row) { + double value = 0.0; + for (std::size_t position = rowOffsets_[row]; + position < rowOffsets_[row + 1U]; + ++position) { + value += values_[position] * rhs[columnIndices_[position]]; + } + result[row] = value; + } + return result; +} + +Status SparseMatrix::validate() const { + return validateCsr( + rows_, columns_, rowOffsets_, columnIndices_, &values_); +} + +SparseMatrix::SparseMatrix( + const std::size_t rows, + const std::size_t columns, + std::vector rowOffsets, + std::vector columnIndices, + std::vector values) + : rows_{rows}, + columns_{columns}, + rowOffsets_{std::move(rowOffsets)}, + columnIndices_{std::move(columnIndices)}, + values_{std::move(values)} {} + +} // namespace fesa diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4014dad..1e6659b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -6,12 +6,14 @@ add_executable( unit/analysis/analysis_model_test.cpp unit/analysis/analysis_state_test.cpp unit/assembly/parallel_for_test.cpp + unit/assembly/sparse_assembler_test.cpp unit/core/diagnostic_test.cpp unit/core/source_identity_test.cpp unit/core/status_test.cpp unit/elements/euler_beam_3d_test.cpp unit/fem/dof_manager_test.cpp unit/math/matrix_test.cpp + unit/math/sparse_matrix_test.cpp unit/math/vector_test.cpp unit/io/abaqus/domain_mapper_test.cpp unit/io/abaqus/input_reader_test.cpp diff --git a/tests/unit/assembly/sparse_assembler_test.cpp b/tests/unit/assembly/sparse_assembler_test.cpp new file mode 100644 index 0000000..bc0edf4 --- /dev/null +++ b/tests/unit/assembly/sparse_assembler_test.cpp @@ -0,0 +1,158 @@ +#include "fesa/analysis/analysis_model.hpp" +#include "fesa/assembly/parallel_for.hpp" +#include "fesa/assembly/sparse_assembler.hpp" +#include "fesa/fem/dof_manager.hpp" +#include "fesa/model/domain.hpp" + +#include + +#include +#include +#include +#include +#include + +namespace { + +fesa::ModelDefinition makeDefinition() { + const std::filesystem::path source{"models/sparse-assembly.inp"}; + fesa::ModelDefinition definition{}; + definition.sourcePath = source; + definition.sourceContentIdentity = "fnv1a64:0123456789abcdef"; + definition.nodes = { + {{"Beam-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 10U}}, + {{"Beam-1", 2, "2"}, {2.0, 0.0, 0.0}, {source, 11U}}, + {{"Beam-1", 3, "3"}, {5.0, 0.0, 0.0}, {source, 12U}}}; + definition.materials = { + {"Material", 120.0, 0.25, {source, 20U}}}; + definition.sections = {{ + "Section", + 2.0, + 1.5, + 0.0, + 0.75, + 0.5, + {0.0, 1.0, 0.0}, + {}, + {source, 30U}}}; + definition.elements = { + {{"Beam-1", 10, "10"}, {0U, 1U}, 0U, 0U, {source, 40U}}, + {{"Beam-1", 20, "20"}, {1U, 2U}, 0U, 0U, {source, 41U}}}; + definition.steps = {{ + "Step-1", {}, {}, 0.1, 1.0, 0.01, 1.0, {source, 50U}}}; + return definition; +} + +template +bool byteIdentical(const std::vector& left, const std::vector& right) { + return left.size() == right.size() && + (left.empty() || + std::memcmp( + left.data(), right.data(), left.size() * sizeof(T)) == 0); +} + +double entry( + const fesa::SparseMatrix& matrix, + const std::size_t row, + const std::size_t column) { + const auto begin = matrix.columnIndices().begin() + matrix.rowOffsets()[row]; + const auto end = matrix.columnIndices().begin() + matrix.rowOffsets()[row + 1U]; + const auto found = std::lower_bound(begin, end, column); + if (found == end || *found != column) { + return 0.0; + } + return matrix.values()[static_cast( + std::distance(matrix.columnIndices().begin(), found))]; +} + +class ReverseParallelFor final : public fesa::ParallelFor { +public: + void execute( + const std::size_t count, + const std::function& body) const override { + ++calls_; + observedCount_ = count; + for (std::size_t index = count; index > 0U; --index) { + body(index - 1U); + } + } + + std::size_t calls() const noexcept { + return calls_; + } + + std::size_t observedCount() const noexcept { + return observedCount_; + } + +private: + mutable std::size_t calls_{0U}; + mutable std::size_t observedCount_{0U}; +}; + +void expectByteIdentical( + const fesa::SparseMatrix& actual, + const fesa::SparseMatrix& expected) { + EXPECT_TRUE(byteIdentical(actual.rowOffsets(), expected.rowOffsets())); + EXPECT_TRUE(byteIdentical(actual.columnIndices(), expected.columnIndices())); + EXPECT_TRUE(byteIdentical(actual.values(), expected.values())); +} + +TEST(SparseAssembly, SerialTbbAndRepeatedRunsAreByteIdentical) { + auto domainResult = fesa::Domain::create(makeDefinition()); + ASSERT_TRUE(domainResult.hasValue()); + auto modelResult = fesa::AnalysisModel::create(domainResult.value()); + ASSERT_TRUE(modelResult.hasValue()); + auto dofsResult = fesa::DofManager::create(modelResult.value()); + ASSERT_TRUE(dofsResult.hasValue()); + + fesa::SerialParallelFor serialExecutor; + fesa::TbbParallelFor tbbExecutor; + ReverseParallelFor reverseExecutor; + auto serial = fesa::SparseAssembler::assembleStiffness( + modelResult.value(), dofsResult.value(), serialExecutor); + auto tbb = fesa::SparseAssembler::assembleStiffness( + modelResult.value(), dofsResult.value(), tbbExecutor); + auto reversed = fesa::SparseAssembler::assembleStiffness( + modelResult.value(), dofsResult.value(), reverseExecutor); + ASSERT_TRUE(serial.hasValue()); + ASSERT_TRUE(tbb.hasValue()); + ASSERT_TRUE(reversed.hasValue()); + + EXPECT_EQ(reverseExecutor.calls(), 1U); + EXPECT_EQ(reverseExecutor.observedCount(), 2U); + EXPECT_EQ(serial.value().rows(), 18U); + EXPECT_EQ(serial.value().columns(), 18U); + EXPECT_EQ(serial.value().rowOffsets(), dofsResult.value().sparsePattern().rowOffsets); + EXPECT_EQ( + serial.value().columnIndices(), + dofsResult.value().sparsePattern().columnIndices); + EXPECT_TRUE(serial.value().validate().isOk()); + expectByteIdentical(tbb.value(), serial.value()); + expectByteIdentical(reversed.value(), serial.value()); + + for (std::size_t repetition = 0U; repetition < 8U; ++repetition) { + auto repeated = fesa::SparseAssembler::assembleStiffness( + modelResult.value(), dofsResult.value(), tbbExecutor); + ASSERT_TRUE(repeated.hasValue()); + expectByteIdentical(repeated.value(), serial.value()); + } + + for (std::size_t row = 0U; row < serial.value().rows(); ++row) { + for (std::size_t column = 0U; + column < serial.value().columns(); + ++column) { + EXPECT_DOUBLE_EQ( + entry(serial.value(), row, column), + entry(serial.value(), column, row)); + } + } + + EXPECT_NEAR(entry(serial.value(), 0U, 0U), 120.0, 1.0e-12); + EXPECT_NEAR(entry(serial.value(), 0U, 6U), -120.0, 1.0e-12); + EXPECT_NEAR(entry(serial.value(), 6U, 6U), 200.0, 1.0e-12); + EXPECT_NEAR(entry(serial.value(), 6U, 12U), -80.0, 1.0e-12); + EXPECT_NEAR(entry(serial.value(), 12U, 12U), 80.0, 1.0e-12); +} + +} // namespace diff --git a/tests/unit/math/sparse_matrix_test.cpp b/tests/unit/math/sparse_matrix_test.cpp new file mode 100644 index 0000000..0f3cfe7 --- /dev/null +++ b/tests/unit/math/sparse_matrix_test.cpp @@ -0,0 +1,149 @@ +#include "fesa/fem/dof_manager.hpp" +#include "fesa/math/matrix.hpp" +#include "fesa/math/sparse_matrix.hpp" + +#include + +#include +#include +#include +#include +#include + +namespace { + +using fesa::CooContribution; +using fesa::SparseMatrix; +using fesa::SparsePattern; +using fesa::Vector; + +static_assert( + !std::is_base_of_v, + "SparseMatrix must own CSR storage independently of dense Matrix."); + +TEST(SparseAssembly, ValidatesKnownCsrAndMultiply) { + const SparsePattern pattern{ + {0U, 2U, 2U, 4U, 5U}, + {0U, 2U, 1U, 3U, 3U}}; + std::vector contributions{ + {2U, 3U, 4.0, 2U, 0U}, + {0U, 2U, 2.0, 0U, 1U}, + {3U, 3U, 5.0, 3U, 0U}, + {0U, 0U, 1.0, 0U, 0U}, + {2U, 1U, 3.0, 1U, 0U}}; + + auto result = SparseMatrix::fromCoo( + 4U, 4U, std::move(contributions), pattern); + ASSERT_TRUE(result.hasValue()); + const SparseMatrix& matrix = result.value(); + + EXPECT_EQ(matrix.rows(), 4U); + EXPECT_EQ(matrix.columns(), 4U); + EXPECT_EQ(matrix.rowOffsets(), pattern.rowOffsets); + EXPECT_EQ(matrix.columnIndices(), pattern.columnIndices); + EXPECT_EQ(matrix.values(), (std::vector{1.0, 2.0, 3.0, 4.0, 5.0})); + EXPECT_TRUE(matrix.validate().isOk()); + + Vector rhs{4U}; + rhs[0U] = 1.0; + rhs[1U] = 2.0; + rhs[2U] = 3.0; + rhs[3U] = 4.0; + const Vector product = matrix.multiply(rhs); + ASSERT_EQ(product.size(), 4U); + EXPECT_DOUBLE_EQ(product[0U], 7.0); + EXPECT_DOUBLE_EQ(product[1U], 0.0); + EXPECT_DOUBLE_EQ(product[2U], 22.0); + EXPECT_DOUBLE_EQ(product[3U], 20.0); + EXPECT_THROW(static_cast(matrix.multiply(Vector{3U})), std::invalid_argument); +} + +TEST(SparseAssembly, ReducesDuplicatesInFixedTupleOrder) { + const SparsePattern pattern{{0U, 1U}, {0U}}; + const std::vector contributions{ + {0U, 0U, 1.0, 2U, 0U}, + {0U, 0U, -1.0e16, 1U, 0U}, + {0U, 0U, 1.0e16, 0U, 0U}}; + + auto first = SparseMatrix::fromCoo(1U, 1U, contributions, pattern); + ASSERT_TRUE(first.hasValue()); + ASSERT_EQ(first.value().values().size(), 1U); + EXPECT_DOUBLE_EQ(first.value().values()[0U], 1.0); + + auto reversedContributions = contributions; + std::reverse(reversedContributions.begin(), reversedContributions.end()); + auto second = SparseMatrix::fromCoo( + 1U, 1U, std::move(reversedContributions), pattern); + ASSERT_TRUE(second.hasValue()); + EXPECT_EQ(second.value().rowOffsets(), first.value().rowOffsets()); + EXPECT_EQ(second.value().columnIndices(), first.value().columnIndices()); + EXPECT_EQ(second.value().values(), first.value().values()); +} + +TEST(SparseAssembly, RejectsInvalidIndexPatternAndShape) { + const SparsePattern oneEntry{{0U, 1U}, {0U}}; + const auto expectFailure = []( + std::size_t rows, + std::size_t columns, + std::vector contributions, + const SparsePattern& pattern) { + auto result = SparseMatrix::fromCoo( + rows, columns, std::move(contributions), pattern); + EXPECT_FALSE(result.hasValue()); + if (!result.hasValue()) { + EXPECT_FALSE(result.status().isOk()); + EXPECT_EQ(result.status().failureCategory(), fesa::FailureCategory::model); + EXPECT_FALSE(result.status().diagnostics().empty()); + } + }; + + expectFailure(2U, 2U, {}, {{0U, 0U}, {}}); + expectFailure(1U, 1U, {}, {{1U, 1U}, {0U}}); + expectFailure(2U, 2U, {}, {{0U, 1U, 0U}, {0U}}); + expectFailure(1U, 2U, {}, {{0U, 2U}, {1U, 0U}}); + expectFailure(1U, 1U, {}, {{0U, 2U}, {0U, 0U}}); + expectFailure(1U, 1U, {}, {{0U, 1U}, {1U}}); + expectFailure(1U, 1U, {{1U, 0U, 1.0, 0U, 0U}}, oneEntry); + expectFailure(1U, 1U, {{0U, 1U, 1.0, 0U, 0U}}, oneEntry); + expectFailure(1U, 2U, {{0U, 1U, 1.0, 0U, 0U}}, {{0U, 1U}, {0U}}); + expectFailure( + 1U, + 1U, + {{0U, 0U, (std::numeric_limits::infinity)(), 0U, 0U}}, + oneEntry); + expectFailure( + 1U, + 1U, + {{0U, 0U, (std::numeric_limits::quiet_NaN)(), 0U, 0U}}, + oneEntry); + expectFailure( + 1U, + 1U, + {{0U, 0U, (std::numeric_limits::max)(), 0U, 0U}, + {0U, 0U, (std::numeric_limits::max)(), 1U, 0U}}, + oneEntry); +} + +TEST(SparseAssembly, PreservesExpectedStructuralZeros) { + const SparsePattern pattern{ + {0U, 2U, 4U, 5U}, + {0U, 2U, 1U, 2U, 0U}}; + std::vector contributions{ + {0U, 0U, 2.0, 0U, 0U}, + {1U, 1U, 4.0, 0U, 1U}, + {1U, 1U, -4.0, 1U, 0U}}; + + auto result = SparseMatrix::fromCoo( + 3U, 3U, std::move(contributions), pattern); + ASSERT_TRUE(result.hasValue()); + EXPECT_EQ(result.value().rowOffsets(), pattern.rowOffsets); + EXPECT_EQ(result.value().columnIndices(), pattern.columnIndices); + EXPECT_EQ( + result.value().values(), + (std::vector{2.0, 0.0, 0.0, 0.0, 0.0})); + EXPECT_EQ( + std::count(result.value().values().begin(), result.value().values().end(), 0.0), + 4); +} + +} // namespace