diff --git a/src/Plugins/OrientationAnalysis/docs/ComputeCAxisLocationsFilter.md b/src/Plugins/OrientationAnalysis/docs/ComputeCAxisLocationsFilter.md index 9183413de0..c5b3d426c7 100644 --- a/src/Plugins/OrientationAnalysis/docs/ComputeCAxisLocationsFilter.md +++ b/src/Plugins/OrientationAnalysis/docs/ComputeCAxisLocationsFilter.md @@ -8,7 +8,7 @@ Statistics (Crystallography) This **Filter** determines the direction of the C-axis for each **Element** by applying the quaternion of the **Element** to the <001> direction, which is the C-axis for *Hexagonal* materials. This will tell where the C-axis of the **Element** sits in the *sample reference frame*. -*Note:* This **Filter** will only work properly for *Hexagonal* materials. The **Filter** does not apply any symmetry operators because there is only one c-axis (<001>) in *Hexagonal* materials and thus all symmetry operators will leave the c-axis in the same position in the sample *reference frame*. However, in *Cubic* materials, for example, the {100} family of directions are all equivalent and the <001> direction will change location in the *sample reference frame* when symmetry operators are applied. +*Note:* This **Filter** will only work properly for *Hexagonal* materials. The **Filter** does not apply any symmetry operators because there is only one c-axis (<001>) in *Hexagonal* materials and thus all symmetry operators will leave the c-axis in the same position in the sample *reference frame*. However, in *Cubic* materials, for example, the {100} family of directions are all equivalent and the <001> direction will change location in the *sample reference frame* when symmetry operators are applied. **Non-hexagonal** phases will have NAN used for their C-axis location. If there are no hexagonal phases present, the filter will emit an error. % Auto generated parameter table will be inserted here diff --git a/src/Plugins/OrientationAnalysis/test/ComputeCAxisLocationsTest.cpp b/src/Plugins/OrientationAnalysis/test/ComputeCAxisLocationsTest.cpp index 1848d59786..2ce4ed746a 100644 --- a/src/Plugins/OrientationAnalysis/test/ComputeCAxisLocationsTest.cpp +++ b/src/Plugins/OrientationAnalysis/test/ComputeCAxisLocationsTest.cpp @@ -2,6 +2,9 @@ #include #include +#include + +#include "simplnx/Common/Constants.hpp" #include "simplnx/Core/Application.hpp" #include "simplnx/DataStructure/AttributeMatrix.hpp" #include "simplnx/DataStructure/Geometry/ImageGeom.hpp" @@ -18,111 +21,301 @@ namespace fs = std::filesystem; namespace { -const std::string k_CAxisLocationsExemplar = "CAxisLocations"; -const std::string k_CAxisLocationsComputed = "NX_CAxisLocations"; +struct FixtureData +{ + std::array inputQuat{}; + std::array expectedOutput{}; +}; + +constexpr float32 k_Sin_OneEighthPiF = 0.38268343f; +constexpr float32 k_Sin_OneEighthPi_OverSqrt3F = k_Sin_OneEighthPiF / k_Sqrt3F; + +const std::vector k_Fixtures = { + // no rotation + {{0.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}}, + // +90 about x + {{k_HalfSqrt2F, 0.0f, 0.0f, k_HalfSqrt2F}, {0.0f, 1.0f, 0.0f}}, + // +90 about y + {{0.0f, k_HalfSqrt2F, 0.0f, k_HalfSqrt2F}, {-1.0f, 0.0f, 0.0f}}, + // +90 about z + {{0.0f, 0.0f, k_HalfSqrt2F, k_HalfSqrt2F}, {0.0f, 0.0f, 1.0f}}, + // 180 about x; flip z sign + {{1.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}, + // 180 about y; flip z sign + {{0.0f, 1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}, + // 180 about z + {{0.0f, 0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}, + // 120 about (1, 1, 1) + {{0.5f, 0.5f, 0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, + // +45 about x + {{k_Sin_OneEighthPiF, 0.0f, 0.0f, k_Cos_OneEighthPiF}, {0.0f, k_HalfSqrt2F, k_HalfSqrt2F}}, + // +45 about y + {{0.0f, k_Sin_OneEighthPiF, 0.0f, k_Cos_OneEighthPiF}, {-k_HalfSqrt2F, 0.0f, k_HalfSqrt2F}}, + // +45 about z + {{0.0f, 0.0f, k_Sin_OneEighthPiF, k_Cos_OneEighthPiF}, {0.0f, 0.0f, 1.0f}}, + // -45 about x + {{-k_Sin_OneEighthPiF, 0.0f, 0.0f, k_Cos_OneEighthPiF}, {0.0f, -k_HalfSqrt2F, k_HalfSqrt2F}}, + // -45 about y + {{0.0f, -k_Sin_OneEighthPiF, 0.0f, k_Cos_OneEighthPiF}, {k_HalfSqrt2F, 0.0f, k_HalfSqrt2F}}, + // -45 about z + {{0.0f, 0.0f, -k_Sin_OneEighthPiF, k_Cos_OneEighthPiF}, {0.0f, 0.0f, 1.0f}}, + // +45 around (1, 1, 1); v_out = ((2 - sqrt(6) - sqrt(2))/6, (2 + sqrt(6) - sqrt(2))/6, (1 + sqrt(2))/3) + {{k_Sin_OneEighthPi_OverSqrt3F, k_Sin_OneEighthPi_OverSqrt3F, k_Sin_OneEighthPi_OverSqrt3F, k_Cos_OneEighthPiF}, {-0.3106172, 0.50587934, 0.80473787}}, +}; + +template +bool ContainsCode(const std::vector& vec, int32 code) +{ + return std::find_if(vec.cbegin(), vec.cend(), [code](const T& item) { return item.code == code; }) != vec.cend(); +} } // namespace -TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: Valid Filter Execution", "[OrientationAnalysis][ComputeCAxisLocationsFilter]") +TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: Preflight Error - Cell array tuple count mismatch (-3520)", "[OrientationAnalysis][ComputeCAxisLocationsFilter][preflight]") +{ + UnitTest::LoadPlugins(); + + // Build a minimal synthetic DataStructure where the two cell-level arrays that are + // validated together (Quats, CellPhases) do NOT share the same tuple count. This drives + // the validateNumberOfTuples() guard in preflightImpl that emits error -3520. + DataStructure dataStructure; + auto* imageGeom = ImageGeom::Create(dataStructure, "DataContainer"); + imageGeom->setDimensions({10, 1, 1}); + + auto* cellAM = AttributeMatrix::Create(dataStructure, "CellData", {10}, imageGeom->getId()); + UnitTest::CreateTestDataArray(dataStructure, "Quats", {10}, {4}, cellAM->getId()); + + // CellPhases lives in a separate AttributeMatrix with a deliberately different tuple + // count (9 != 10) so the cross-array tuple-count check fails. + auto* mismatchAM = AttributeMatrix::Create(dataStructure, "MismatchData", {9}, imageGeom->getId()); + UnitTest::CreateTestDataArray(dataStructure, "Phases", {9}, {1}, mismatchAM->getId()); + + auto* ensembleAM = AttributeMatrix::Create(dataStructure, "CellEnsembleData", {2}, imageGeom->getId()); + UnitTest::CreateTestDataArray(dataStructure, "CrystalStructures", {2}, {1}, ensembleAM->getId()); + + ComputeCAxisLocationsFilter filter; + Arguments args; + args.insertOrAssign(ComputeCAxisLocationsFilter::k_QuatsArrayPath_Key, std::make_any(DataPath({"DataContainer", "CellData", "Quats"}))); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CellPhasesArrayPath_Key, std::make_any(DataPath({"DataContainer", "MismatchData", "Phases"}))); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CrystalStructuresArrayPath_Key, std::make_any(DataPath({"DataContainer", "CellEnsembleData", "CrystalStructures"}))); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CAxisLocationsArrayName_Key, std::make_any("CAxisLocation")); + + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_INVALID(preflightResult.outputActions); + REQUIRE(preflightResult.outputActions.errors().size() == 1); + REQUIRE(preflightResult.outputActions.errors()[0].code == -3520); + + UnitTest::CheckArraysInheritTupleDims(dataStructure); +} + +TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: No hexagonal phases error", "[OrientationAnalysis][ComputeCAxisLocationsFilter]") { UnitTest::LoadPlugins(); - const nx::core::UnitTest::TestFileSentinel testDataSentinel(nx::core::unit_test::k_TestFilesDir, "caxis_data.tar.gz", "caxis_data"); + DataStructure dataStructure; + UnitTest::CreateTestDataArray(dataStructure, "Quats", {10}, {4}); + UnitTest::CreateTestDataArray(dataStructure, "Phases", {10}, {1}); - // Read Exemplar DREAM3D File Filter - auto exemplarFilePath = fs::path(fmt::format("{}/caxis_data/7_0_find_caxis_data.dream3d", unit_test::k_TestFilesDir)); - DataStructure dataStructure = UnitTest::LoadDataStructure(exemplarFilePath); + // Create crystal structures array with no hexagonal phases + UInt32Array* crystalStructures = UnitTest::CreateTestDataArray(dataStructure, "CrystalStructures", {2}, {1}); + crystalStructures->setValue(0, ebsdlib::CrystalStructure::UnknownCrystalStructure); + crystalStructures->setValue(1, ebsdlib::CrystalStructure::Cubic_High); - // Instantiate the filter, a DataStructure object and an Arguments Object ComputeCAxisLocationsFilter filter; Arguments args; - // Create default Parameters for the filter. - args.insertOrAssign(ComputeCAxisLocationsFilter::k_QuatsArrayPath_Key, std::make_any(k_QuatsArrayPath)); - args.insertOrAssign(ComputeCAxisLocationsFilter::k_CellPhasesArrayPath_Key, std::make_any(k_PhasesArrayPath)); - args.insertOrAssign(ComputeCAxisLocationsFilter::k_CrystalStructuresArrayPath_Key, std::make_any(k_CrystalStructuresArrayPath)); - args.insertOrAssign(ComputeCAxisLocationsFilter::k_CAxisLocationsArrayName_Key, std::make_any(k_CAxisLocationsComputed)); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_QuatsArrayPath_Key, std::make_any(std::vector{"Quats"})); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CellPhasesArrayPath_Key, std::make_any(std::vector{"Phases"})); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CrystalStructuresArrayPath_Key, std::make_any(std::vector{"CrystalStructures"})); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CAxisLocationsArrayName_Key, std::make_any("CAxisLocations")); - // Preflight the filter and check result auto preflightResult = filter.preflight(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions); - // Execute the filter and check the result auto executeResult = filter.execute(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) + SIMPLNX_RESULT_REQUIRE_INVALID(executeResult.result); - UnitTest::CompareFloatArraysWithNans(dataStructure, k_CellAttributeMatrix.createChildPath(k_CAxisLocationsExemplar), k_CellAttributeMatrix.createChildPath(k_CAxisLocationsComputed), - UnitTest::EPSILON, false); + REQUIRE(ContainsCode(executeResult.result.errors(), -3522)); UnitTest::CheckArraysInheritTupleDims(dataStructure); } -TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: InValid Filter Execution") +TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: Not all hexagonal phases warning", "[OrientationAnalysis][ComputeCAxisLocationsFilter]") { UnitTest::LoadPlugins(); - const nx::core::UnitTest::TestFileSentinel testDataSentinel(nx::core::unit_test::k_TestFilesDir, "caxis_data.tar.gz", "caxis_data"); + DataStructure dataStructure; + UnitTest::CreateTestDataArray(dataStructure, "Quats", {10}, {4}); + + // Create crystal structures array with some non-hexagonal phases + UInt32Array* crystalStructures = UnitTest::CreateTestDataArray(dataStructure, "CrystalStructures", {3}, {1}); + crystalStructures->setValue(0, ebsdlib::CrystalStructure::UnknownCrystalStructure); + crystalStructures->setValue(1, ebsdlib::CrystalStructure::Hexagonal_High); + crystalStructures->setValue(2, ebsdlib::CrystalStructure::Cubic_High); - // Read Exemplar DREAM3D File Filter - auto exemplarFilePath = fs::path(fmt::format("{}/caxis_data/7_0_find_caxis_data.dream3d", unit_test::k_TestFilesDir)); - DataStructure dataStructure = UnitTest::LoadDataStructure(exemplarFilePath); + // All non-hexagonal phases + Int32Array* phases = UnitTest::CreateTestDataArray(dataStructure, "Phases", {10}, {1}); + for(usize i = 0; i < phases->getSize(); i++) + { + phases->setValue(i, 2); + } - auto& crystalStructs = dataStructure.getDataRefAs(k_CrystalStructuresArrayPath); - crystalStructs[1] = 1; + DataPath cAxisLocationsPath({"CAxisLocations"}); - // Instantiate the filter, a DataStructure object and an Arguments Object ComputeCAxisLocationsFilter filter; Arguments args; - // Create default Parameters for the filter. - args.insertOrAssign(ComputeCAxisLocationsFilter::k_QuatsArrayPath_Key, std::make_any(k_QuatsArrayPath)); - args.insertOrAssign(ComputeCAxisLocationsFilter::k_CellPhasesArrayPath_Key, std::make_any(k_PhasesArrayPath)); - args.insertOrAssign(ComputeCAxisLocationsFilter::k_CrystalStructuresArrayPath_Key, std::make_any(k_CrystalStructuresArrayPath)); - args.insertOrAssign(ComputeCAxisLocationsFilter::k_CAxisLocationsArrayName_Key, std::make_any(k_CAxisLocationsComputed)); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_QuatsArrayPath_Key, std::make_any(std::vector{"Quats"})); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CellPhasesArrayPath_Key, std::make_any(std::vector{"Phases"})); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CrystalStructuresArrayPath_Key, std::make_any(std::vector{"CrystalStructures"})); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CAxisLocationsArrayName_Key, std::make_any(cAxisLocationsPath.getTargetName())); - // Preflight the filter and check result auto preflightResult = filter.preflight(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions); + + // Check for unconditional preflight warning about ensuring nonhexagonal data + REQUIRE(ContainsCode(preflightResult.outputActions.warnings(), -3521)); - // Execute the filter and check the result auto executeResult = filter.execute(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_INVALID(executeResult.result) + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result); + + REQUIRE(ContainsCode(executeResult.result.warnings(), -3523)); + + REQUIRE(dataStructure.containsData(cAxisLocationsPath)); + const auto& cAxisLocations = dataStructure.getDataRefAs(cAxisLocationsPath); + REQUIRE(std::all_of(cAxisLocations.cbegin(), cAxisLocations.cend(), [](float32 value) { return std::isnan(value); })); UnitTest::CheckArraysInheritTupleDims(dataStructure); } -TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: Preflight Error - Cell array tuple count mismatch (-3520)", "[OrientationAnalysis][ComputeCAxisLocationsFilter][preflight]") +TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle", "[OrientationAnalysis][ComputeCAxisLocationsFilter]") { UnitTest::LoadPlugins(); - // Build a minimal synthetic DataStructure where the two cell-level arrays that are - // validated together (Quats, CellPhases) do NOT share the same tuple count. This drives - // the validateNumberOfTuples() guard in preflightImpl that emits error -3520. DataStructure dataStructure; - auto* imageGeom = ImageGeom::Create(dataStructure, "DataContainer"); - imageGeom->setDimensions({10, 1, 1}); - auto* cellAM = AttributeMatrix::Create(dataStructure, "CellData", {10}, imageGeom->getId()); - UnitTest::CreateTestDataArray(dataStructure, "Quats", {10}, {4}, cellAM->getId()); + const usize size = k_Fixtures.size(); - // CellPhases lives in a separate AttributeMatrix with a deliberately different tuple - // count (9 != 10) so the cross-array tuple-count check fails. - auto* mismatchAM = AttributeMatrix::Create(dataStructure, "MismatchData", {9}, imageGeom->getId()); - UnitTest::CreateTestDataArray(dataStructure, "Phases", {9}, {1}, mismatchAM->getId()); + Float32Array* quats = UnitTest::CreateTestDataArray(dataStructure, "Quats", {size}, {4}); - auto* ensembleAM = AttributeMatrix::Create(dataStructure, "CellEnsembleData", {2}, imageGeom->getId()); - UnitTest::CreateTestDataArray(dataStructure, "CrystalStructures", {2}, {1}, ensembleAM->getId()); + for(usize i = 0; i < size; i++) + { + for(usize j = 0; j < 4; j++) + { + quats->setComponent(i, j, k_Fixtures[i].inputQuat[j]); + } + } + + UInt32Array* crystalStructures = UnitTest::CreateTestDataArray(dataStructure, "CrystalStructures", {2}, {1}); + crystalStructures->setValue(0, ebsdlib::CrystalStructure::UnknownCrystalStructure); + crystalStructures->setValue(1, ebsdlib::CrystalStructure::Hexagonal_High); + + Int32Array* phases = UnitTest::CreateTestDataArray(dataStructure, "Phases", {size}, {1}); + for(usize i = 0; i < phases->getSize(); i++) + { + phases->setValue(i, 1); + } + + DataPath cAxisLocationsPath({"CAxisLocations"}); ComputeCAxisLocationsFilter filter; Arguments args; - args.insertOrAssign(ComputeCAxisLocationsFilter::k_QuatsArrayPath_Key, std::make_any(DataPath({"DataContainer", "CellData", "Quats"}))); - args.insertOrAssign(ComputeCAxisLocationsFilter::k_CellPhasesArrayPath_Key, std::make_any(DataPath({"DataContainer", "MismatchData", "Phases"}))); - args.insertOrAssign(ComputeCAxisLocationsFilter::k_CrystalStructuresArrayPath_Key, std::make_any(DataPath({"DataContainer", "CellEnsembleData", "CrystalStructures"}))); - args.insertOrAssign(ComputeCAxisLocationsFilter::k_CAxisLocationsArrayName_Key, std::make_any("CAxisLocation")); + + args.insertOrAssign(ComputeCAxisLocationsFilter::k_QuatsArrayPath_Key, std::make_any(std::vector{"Quats"})); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CellPhasesArrayPath_Key, std::make_any(std::vector{"Phases"})); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CrystalStructuresArrayPath_Key, std::make_any(std::vector{"CrystalStructures"})); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CAxisLocationsArrayName_Key, std::make_any(cAxisLocationsPath.getTargetName())); auto preflightResult = filter.preflight(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_INVALID(preflightResult.outputActions); - REQUIRE(preflightResult.outputActions.errors().size() == 1); - REQUIRE(preflightResult.outputActions.errors()[0].code == -3520); + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions); + + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result); + + REQUIRE(dataStructure.containsData(cAxisLocationsPath)); + auto& cAxisLocations = dataStructure.getDataRefAs(cAxisLocationsPath); + for(usize i = 0; i < size; i++) + { + for(usize j = 0; j < 3; j++) + { + INFO(fmt::format("i = {} | j = {} | input_quat = ({}) | expected_vec = ({})", i, j, fmt::join(k_Fixtures[i].inputQuat, ", "), fmt::join(k_Fixtures[i].expectedOutput, ", "))); + REQUIRE(cAxisLocations.getComponent(i, j) == Approx(k_Fixtures[i].expectedOutput[j]).margin(1e-7f)); + } + } + + UnitTest::CheckArraysInheritTupleDims(dataStructure); +} + +TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle - Mixed hexagonal and non-hexagonal phases", "[OrientationAnalysis][ComputeCAxisLocationsFilter]") +{ + UnitTest::LoadPlugins(); + + DataStructure dataStructure; + + const usize size = k_Fixtures.size(); + + Float32Array* quats = UnitTest::CreateTestDataArray(dataStructure, "Quats", {size}, {4}); + + for(usize i = 0; i < size; i++) + { + for(usize j = 0; j < 4; j++) + { + quats->setComponent(i, j, k_Fixtures[i].inputQuat[j]); + } + } + + UInt32Array* crystalStructures = UnitTest::CreateTestDataArray(dataStructure, "CrystalStructures", {3}, {1}); + crystalStructures->setValue(0, ebsdlib::CrystalStructure::UnknownCrystalStructure); + crystalStructures->setValue(1, ebsdlib::CrystalStructure::Hexagonal_High); + crystalStructures->setValue(2, ebsdlib::CrystalStructure::Cubic_High); + + // Alternate hexagonal (phase 1) and cubic (phase 2) cells so a single execution exercises + // both the computed c-axis path and the non-hexagonal NaN path, and verifies the NaN + // branch does not disturb neighboring computed values. + Int32Array* phases = UnitTest::CreateTestDataArray(dataStructure, "Phases", {size}, {1}); + for(usize i = 0; i < size; i++) + { + phases->setValue(i, i % 2 == 0 ? 1 : 2); + } + + DataPath cAxisLocationsPath({"CAxisLocations"}); + + ComputeCAxisLocationsFilter filter; + Arguments args; + + args.insertOrAssign(ComputeCAxisLocationsFilter::k_QuatsArrayPath_Key, std::make_any(std::vector{"Quats"})); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CellPhasesArrayPath_Key, std::make_any(std::vector{"Phases"})); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CrystalStructuresArrayPath_Key, std::make_any(std::vector{"CrystalStructures"})); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CAxisLocationsArrayName_Key, std::make_any(cAxisLocationsPath.getTargetName())); + + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions); + + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result); + + REQUIRE(ContainsCode(executeResult.result.warnings(), -3523)); + + REQUIRE(dataStructure.containsData(cAxisLocationsPath)); + auto& cAxisLocations = dataStructure.getDataRefAs(cAxisLocationsPath); + for(usize i = 0; i < size; i++) + { + const bool isHexCell = i % 2 == 0; + for(usize j = 0; j < 3; j++) + { + INFO(fmt::format("i = {} | j = {} | phase = {} | input_quat = ({}) | expected_vec = ({})", i, j, phases->getValue(i), fmt::join(k_Fixtures[i].inputQuat, ", "), + fmt::join(k_Fixtures[i].expectedOutput, ", "))); + if(isHexCell) + { + REQUIRE(cAxisLocations.getComponent(i, j) == Approx(k_Fixtures[i].expectedOutput[j]).margin(1e-7f)); + } + else + { + REQUIRE(std::isnan(cAxisLocations.getComponent(i, j))); + } + } + } + + UnitTest::CheckArraysInheritTupleDims(dataStructure); } TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: SIMPL Backwards Compatibility", "[OrientationAnalysis][ComputeCAxisLocationsFilter][BackwardsCompatibility]") diff --git a/src/Plugins/OrientationAnalysis/vv/ComputeCAxisLocationsFilter.md b/src/Plugins/OrientationAnalysis/vv/ComputeCAxisLocationsFilter.md new file mode 100644 index 0000000000..61c0b8f2f6 --- /dev/null +++ b/src/Plugins/OrientationAnalysis/vv/ComputeCAxisLocationsFilter.md @@ -0,0 +1,107 @@ +# V&V Report: ComputeCAxisLocationsFilter + +| | | +|--------|--------------| +| Plugin | OrientationAnalysis | +| SIMPLNX UUID | `a51c257a-ddc1-499a-9b21-f2d25a19d098` | +| DREAM3D 6.5.171 equivalent | `FindCAxisLocations` (SIMPL UUID `68ae7b7e-b9f7-5799-9f82-ce21d0ccd55e`) - `Source/Plugins/OrientationAnalysis/OrientationAnalysisFilters/FindCAxisLocations.{h,cpp}` | +| Verified commit | ** | +| Status | READY FOR REVIEW | +| Sign-off | ** | + +## At a glance + +A scannable dashboard for reviewers. Each row is one sentence to one short paragraph — enough that a reader can decide whether they need to read the long-form sections below. + +| Aspect | Current state | +|------------------------|------------------------------------------------------------------------------------------------------------------------------| +| Algorithm Relationship | Port - The EbsdLib, matrix math, and SIMPL APIs have changed but the code is functionally identical. Addition of several error branches when the crystal structure type is not hexagonal. | +| Oracle (confirmed) | Class 1 (Analytical) - 15 hand derived data fixtures | +| Code paths enumerated | 7 of 8 paths exercised - only the filter cancelation path is untested | +| Tests today | 5 test cases - 1 test with Class 1 Oracle, 2 error path tests, 1 warning path test, 1 SIMPL json backwards compatibility test | +| Exemplar archive | None - removed test using circular oracle data from `caxis_data.tar.gz` | +| Legacy comparison | Run against DREAM3D 6.5.171 with the inline test data for the Class 1 Oracle. Result is bit-identical between 6.5.171 and NX for hexagonal phases. | +| Bug flags | None | +| V&V phase | Ready for review | + +For worked instances see `src/Plugins/OrientationAnalysis/vv/BadDataNeighborOrientationCheckFilter.md` and `src/Plugins/OrientationAnalysis/vv/ComputeAvgCAxesFilter.md` (on `topic/vv/compute_avg_caxis`). + +## Summary + +ComputeCAxisLocationsFilter determines the direction of the C-axis for each element, in the *sample reference frame*, by applying the quaternion of the element to the <001> direction, which is the C-axis for *Hexagonal* materials. + +The filter is verified with a Class 1 (Analytical) oracle. The filter uses the quaternion to rotate the C-axis into the sample reference frame. This is done by converting the quaternion to a rotation matrix. Then the transpose of the matrix is used due to DREAM3D conventions (see `wrapping/python/docs/source/Reference_Frame_Notes.md`). Due to the transpose the sign of the third element may need to be flipped. A new test was added with handed verified data. + +There were no deviations that affect the output found for hexagonal materials. + +## Algorithm Relationship + +*Classification*: Port + +*Evidence*: Same loop with the same rotation equation used + +- UUID changed from SIMPL and filter renamed to match "Compute" naming conventions. +- EbsdLib is now up to version 3 which has equivalent but changed API for orientations like quaternions. The internal matrix math API has also changed here to use EbsdLib and Eigen but is functionally identical. +- Added one error branch before main execution checking for at least one hexagonal phase +- Added one warning branch before main execution for when not all phases are hexagonal +- Added branch inside loop to set not hexagonal quaternions to NAN to identify them as invalid + +*PR(s):* + +- **PR #576** ("FILTER: FindCAxisLocations & FindFeatureNeighborCAxisMisalignment filters") - Initial PR +- **PR #801** ("ENH: Rename complex to simplnx") - Library rename +- **PR #956** ("ENH: Rename Filters that start with Find/Generate/Calculate to Compute") - Filter rename +- **PR #1438** ("ENH: Microtexture related filter cleanup") - Header include changed +- **PR #1472** ("ENH: Update to EbsdLib 2.0.0 API") - EbsdLib 2.0.0 API update +- **PR #1582** ("ENH: Add missing cancel checks to lots of filters") - Added cancel check in loop + +## Oracle + +*Class:* **Class 1 (Analytical)** + +*Applied:* Handed derived output of C-axis locations from quaternions. The expected outputs agree between DREAM3DNX, DREAM3D 6.5.171, and manual calculations (`v_passive ​= Rᵀv` with z component forced to positive). Includes 15 different orientations about x, y, and z at different angles. Using the previous formula, the exact form results were produced and compared against DREAM3D output. + +*Encoded:* *`test/ComputeCAxisLocationsTest.cpp::"OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle"` - 15 fixtures, all pass.* + +*Second-engineer review:* *Pending* + +## Code path coverage + +*7 of 8 paths exercised. The non-covered path is the cancellation branch which is not currently able to be tested for all filters* + +Source: `src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeCAxisLocations.cpp` (107 lines). + +| # | Phase | Path | Test case | +|----|-----------------|---------------------------------------------------|--------------------------------------------| +| 1 | *Preflight* | Tuple validity check (-3520 error code) | "OrientationAnalysis::ComputeCAxisLocationsFilter: Preflight Error - Cell array tuple count mismatch (-3520)" | +| 2 | *Preflight* | Unconditional warning advising user to ensure their data contains hexagonal phases | "OrientationAnalysis::ComputeCAxisLocationsFilter: Not all hexagonal phases warning" | +| 3 | *Execute* | No hexagonal phases check (-3522 error code) | "OrientationAnalysis::ComputeCAxisLocationsFilter: No hexagonal phases error" | +| 4 | *Execute* | Not all phases hexagonal check (-3523 warning code) | "OrientationAnalysis::ComputeCAxisLocationsFilter: Not all hexagonal phases warning" | +| 5 | *Execute - per-cell* | Should cancel check | Not directly tested - no filter cancellation testing infrastructure | +| 6 | *Execute - per-cell* | Hexagonal C-axis location calculation path | "OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle" | +| 7 | *Execute - per-cell* | C-axis direction flip check | "OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle" | +| 8 | *Execute - per-cell* | Non-hexagonal NAN branch | "OrientationAnalysis::ComputeCAxisLocationsFilter: Not all hexagonal phases warning" | + +## Test inventory + +| Test case | Status | Notes | +|-----------|--------|-------| +| "OrientationAnalysis::ComputeCAxisLocationsFilter: Valid Filter Execution" | retired | Circular oracle | +| "OrientationAnalysis::ComputeCAxisLocationsFilter: InValid Filter Execution" | retired | Superseded by more specific test "OrientationAnalysis::ComputeCAxisLocationsFilter: No hexagonal phases error" | +| "OrientationAnalysis::ComputeCAxisLocationsFilter: Preflight Error - Cell array tuple count mismatch (-3520)" | kept | Covers preflight error for tuple mismatch of phases and quats | +| "OrientationAnalysis::ComputeCAxisLocationsFilter: SIMPL Backwards Compatibility" | kept | Covers SIMPL json backwards compatibility | +| "OrientationAnalysis::ComputeCAxisLocationsFilter: No hexagonal phases error" | new-for-V&V | Covers no hexagonal phases branch | +| "OrientationAnalysis::ComputeCAxisLocationsFilter: Not all hexagonal phases warning" | new-for-V&V | Covers non-hexagonal branch | +| "OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle" | new-for-V&V | Covers hand calculated quaternions which also agree with DREAM3D 6.5.171. Also covers the sign flip path. | + +## Exemplar archive + +No new exemplar archive was created for this V&V cycle: the Class 1 oracle is encoded entirely as inline expected values in the test source. Tests no longer use circular oracle `caxis_data.tar.gz`. + +## Deviations from DREAM3D 6.5.171 + +Comparison run on "OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle" between NX and 6.5.171 with bit-identical output for hexagonal phases. The legacy comparison used the inline data **"OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle"**: These inline quaternion values were encoded into a csv file. This was then read into a 6.5.171 pipeline with the `FindCAxisLocations` filter and wrote out to a DREAM3D file. The output of NX test was also run and written to a file. These outputs were then verified to have no differences by using `h5py` to read both files and compare the values. + +- D1 - non-hexagonal cells: NaN (NX) vs meaningless-but-finite computed value (6.5.171). NX intentional improvement; no legacy fix warranted. +- D2 - no hexagonal phases present: hard error -3522 (NX) vs silent full execution (6.5.171). +- D3 - added warnings -3521 (preflight, unconditional) and -3523 (execute, mixed phases); legacy emits none. diff --git a/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/pipelines/legacy_6_5_171.json b/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/pipelines/legacy_6_5_171.json deleted file mode 100644 index 279ff43d1b..0000000000 --- a/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/pipelines/legacy_6_5_171.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "0": { - "FilterVersion": "1.0.0", - "Filter_Human_Label": "Create Data Container", - "Filter_Name": "CreateDataContainer", - "Filter_Uuid": "{816fbe6b-7c38-581b-b149-3f839fb65b93}", - "DataContainerName": "DataContainer" - }, - "1": { - "AttributeMatrixType": 3, - "CreatedAttributeMatrix": { "Attribute Matrix Name": "CellData", "Data Array Name": "", "Data Container Name": "DataContainer" }, - "FilterVersion": "1.1.572", - "Filter_Human_Label": "Create Attribute Matrix", - "Filter_Name": "CreateAttributeMatrix", - "Filter_Uuid": "{93375ef0-7367-5372-addc-baa019b1b341}", - "TupleDimensions": { - "Column Headers": ["0"], - "DefaultColCount": 0, "DefaultRowCount": 0, - "HasDynamicCols": true, "HasDynamicRows": false, - "MinColCount": 0, "MinRowCount": 0, - "Row Headers": ["0"], - "Table Data": [[1]] - } - }, - "2": { - "FilterVersion": "1.0.359", - "Filter_Human_Label": "Create Data Array", - "Filter_Name": "CreateDataArray", - "Filter_Uuid": "{77f392fb-c1eb-57da-a1b1-e7acf9239fb8}", - "InitializationRange": { "Max": 0, "Min": 0 }, - "InitializationType": 0, - "InitializationValue": "0.785398185", - "NewArray": { "Attribute Matrix Name": "CellData", "Data Array Name": "eu0", "Data Container Name": "DataContainer" }, - "NumberOfComponents": 1, - "ScalarType": 8 - }, - "3": { - "FilterVersion": "1.0.359", - "Filter_Human_Label": "Create Data Array", - "Filter_Name": "CreateDataArray", - "Filter_Uuid": "{77f392fb-c1eb-57da-a1b1-e7acf9239fb8}", - "InitializationRange": { "Max": 0, "Min": 0 }, - "InitializationType": 0, - "InitializationValue": "0.52359879", - "NewArray": { "Attribute Matrix Name": "CellData", "Data Array Name": "eu1", "Data Container Name": "DataContainer" }, - "NumberOfComponents": 1, - "ScalarType": 8 - }, - "4": { - "FilterVersion": "1.0.359", - "Filter_Human_Label": "Create Data Array", - "Filter_Name": "CreateDataArray", - "Filter_Uuid": "{77f392fb-c1eb-57da-a1b1-e7acf9239fb8}", - "InitializationRange": { "Max": 0, "Min": 0 }, - "InitializationType": 0, - "InitializationValue": "1.04719758", - "NewArray": { "Attribute Matrix Name": "CellData", "Data Array Name": "eu2", "Data Container Name": "DataContainer" }, - "NumberOfComponents": 1, - "ScalarType": 8 - }, - "5": { - "FilterVersion": "1.0.132", - "Filter_Human_Label": "Combine Attribute Arrays", - "Filter_Name": "CombineAttributeArrays", - "NormalizeData": 0, - "SelectedDataArrayPaths": [ - { "Attribute Matrix Name": "CellData", "Data Array Name": "eu0", "Data Container Name": "DataContainer" }, - { "Attribute Matrix Name": "CellData", "Data Array Name": "eu1", "Data Container Name": "DataContainer" }, - { "Attribute Matrix Name": "CellData", "Data Array Name": "eu2", "Data Container Name": "DataContainer" } - ], - "StackedDataArrayName": "EulerAngles" - }, - "6": { - "FilterVersion": "1.0.0", "Filter_Human_Label": "Convert Orientation Representation", "Filter_Name": "ConvertOrientations", - "Filter_Uuid": "{e5629880-98c4-5656-82b8-c9fe2b9744de}", - "InputType": 0, "OutputType": 1, - "InputOrientationArrayPath": { "Attribute Matrix Name": "CellData", "Data Array Name": "EulerAngles", "Data Container Name": "DataContainer" }, - "OutputOrientationArrayName": "leg_om" - }, - "7": { - "FilterVersion": "1.0.0", "Filter_Human_Label": "Convert Orientation Representation", "Filter_Name": "ConvertOrientations", - "Filter_Uuid": "{e5629880-98c4-5656-82b8-c9fe2b9744de}", - "InputType": 0, "OutputType": 2, - "InputOrientationArrayPath": { "Attribute Matrix Name": "CellData", "Data Array Name": "EulerAngles", "Data Container Name": "DataContainer" }, - "OutputOrientationArrayName": "leg_qu" - }, - "8": { - "FilterVersion": "1.0.0", "Filter_Human_Label": "Convert Orientation Representation", "Filter_Name": "ConvertOrientations", - "Filter_Uuid": "{e5629880-98c4-5656-82b8-c9fe2b9744de}", - "InputType": 0, "OutputType": 3, - "InputOrientationArrayPath": { "Attribute Matrix Name": "CellData", "Data Array Name": "EulerAngles", "Data Container Name": "DataContainer" }, - "OutputOrientationArrayName": "leg_ax" - }, - "9": { - "FilterVersion": "1.0.0", "Filter_Human_Label": "Convert Orientation Representation", "Filter_Name": "ConvertOrientations", - "Filter_Uuid": "{e5629880-98c4-5656-82b8-c9fe2b9744de}", - "InputType": 0, "OutputType": 4, - "InputOrientationArrayPath": { "Attribute Matrix Name": "CellData", "Data Array Name": "EulerAngles", "Data Container Name": "DataContainer" }, - "OutputOrientationArrayName": "leg_ro" - }, - "10": { - "FilterVersion": "1.0.0", "Filter_Human_Label": "Convert Orientation Representation", "Filter_Name": "ConvertOrientations", - "Filter_Uuid": "{e5629880-98c4-5656-82b8-c9fe2b9744de}", - "InputType": 0, "OutputType": 5, - "InputOrientationArrayPath": { "Attribute Matrix Name": "CellData", "Data Array Name": "EulerAngles", "Data Container Name": "DataContainer" }, - "OutputOrientationArrayName": "leg_ho" - }, - "11": { - "FilterVersion": "1.0.0", "Filter_Human_Label": "Convert Orientation Representation", "Filter_Name": "ConvertOrientations", - "Filter_Uuid": "{e5629880-98c4-5656-82b8-c9fe2b9744de}", - "InputType": 0, "OutputType": 6, - "InputOrientationArrayPath": { "Attribute Matrix Name": "CellData", "Data Array Name": "EulerAngles", "Data Container Name": "DataContainer" }, - "OutputOrientationArrayName": "leg_cu" - }, - "12": { - "FilterVersion": "1.2.724", - "Filter_Human_Label": "Write DREAM.3D Data File", - "Filter_Name": "DataContainerWriter", - "Filter_Uuid": "{3fcd4c43-9d75-5b86-aad4-4441bc914f37}", - "OutputFile": "/Users/mjackson/Workspace9/simplnx/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/results/legacy_out.dream3d", - "WriteTimeSeries": 0, - "WriteXdmfFile": 0 - }, - "PipelineBuilder": { - "Name": "ConvertOrientations Legacy A/B", - "Number_Filters": 13, - "Version": 6 - } -} diff --git a/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/pipelines/nx.d3dpipeline b/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/pipelines/nx.d3dpipeline deleted file mode 100644 index 22866c6eeb..0000000000 --- a/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/pipelines/nx.d3dpipeline +++ /dev/null @@ -1,96 +0,0 @@ -{ - "isDisabled": false, - "name": "nx.d3dpipeline", - "pinnedParams": [], - "workflowParams": [], - "pipeline": [ - { - "args": { - "import_data_object": { - "value": { "data_paths": [], "file_path": "/Users/mjackson/Workspace9/simplnx/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/results/legacy_out.dream3d", "path_import_policy": 0 }, - "version": 2 - }, - "parameters_version": 1 - }, - "comments": "Read the legacy 6.5.171 output so the EulerAngles input is byte-identical to the legacy run.", - "filter": { "name": "nx::core::ReadDREAM3DFilter", "uuid": "0dbd31c7-19e0-4077-83ef-f4a6459a0e2d" }, - "isDisabled": false - }, - { - "args": { - "input_orientation_array_path": { "value": "DataContainer/CellData/EulerAngles", "version": 1 }, - "input_representation_index": { "value": 0, "version": 1 }, - "output_orientation_array_name": { "value": "nx_om", "version": 1 }, - "output_representation_index": { "value": 1, "version": 1 }, - "parameters_version": 1 - }, - "filter": { "name": "nx::core::ConvertOrientationsFilter", "uuid": "501e54e6-a66f-4eeb-ae37-00e649c00d4b" }, - "isDisabled": false - }, - { - "args": { - "input_orientation_array_path": { "value": "DataContainer/CellData/EulerAngles", "version": 1 }, - "input_representation_index": { "value": 0, "version": 1 }, - "output_orientation_array_name": { "value": "nx_qu", "version": 1 }, - "output_representation_index": { "value": 2, "version": 1 }, - "parameters_version": 1 - }, - "filter": { "name": "nx::core::ConvertOrientationsFilter", "uuid": "501e54e6-a66f-4eeb-ae37-00e649c00d4b" }, - "isDisabled": false - }, - { - "args": { - "input_orientation_array_path": { "value": "DataContainer/CellData/EulerAngles", "version": 1 }, - "input_representation_index": { "value": 0, "version": 1 }, - "output_orientation_array_name": { "value": "nx_ax", "version": 1 }, - "output_representation_index": { "value": 3, "version": 1 }, - "parameters_version": 1 - }, - "filter": { "name": "nx::core::ConvertOrientationsFilter", "uuid": "501e54e6-a66f-4eeb-ae37-00e649c00d4b" }, - "isDisabled": false - }, - { - "args": { - "input_orientation_array_path": { "value": "DataContainer/CellData/EulerAngles", "version": 1 }, - "input_representation_index": { "value": 0, "version": 1 }, - "output_orientation_array_name": { "value": "nx_ro", "version": 1 }, - "output_representation_index": { "value": 4, "version": 1 }, - "parameters_version": 1 - }, - "filter": { "name": "nx::core::ConvertOrientationsFilter", "uuid": "501e54e6-a66f-4eeb-ae37-00e649c00d4b" }, - "isDisabled": false - }, - { - "args": { - "input_orientation_array_path": { "value": "DataContainer/CellData/EulerAngles", "version": 1 }, - "input_representation_index": { "value": 0, "version": 1 }, - "output_orientation_array_name": { "value": "nx_ho", "version": 1 }, - "output_representation_index": { "value": 5, "version": 1 }, - "parameters_version": 1 - }, - "filter": { "name": "nx::core::ConvertOrientationsFilter", "uuid": "501e54e6-a66f-4eeb-ae37-00e649c00d4b" }, - "isDisabled": false - }, - { - "args": { - "input_orientation_array_path": { "value": "DataContainer/CellData/EulerAngles", "version": 1 }, - "input_representation_index": { "value": 0, "version": 1 }, - "output_orientation_array_name": { "value": "nx_cu", "version": 1 }, - "output_representation_index": { "value": 6, "version": 1 }, - "parameters_version": 1 - }, - "filter": { "name": "nx::core::ConvertOrientationsFilter", "uuid": "501e54e6-a66f-4eeb-ae37-00e649c00d4b" }, - "isDisabled": false - }, - { - "args": { - "export_file_path": { "value": "/Users/mjackson/Workspace9/simplnx/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/results/nx_out.dream3d", "version": 1 }, - "parameters_version": 1, - "write_xdmf_file": { "value": false, "version": 1 } - }, - "filter": { "name": "nx::core::WriteDREAM3DFilter", "uuid": "b3a95784-2ced-41ec-8d3d-0242ac130003" }, - "isDisabled": false - } - ], - "version": 1 -} diff --git a/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/results/.gitignore b/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/results/.gitignore deleted file mode 100644 index 90f47baddc..0000000000 --- a/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/results/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Regenerable A/B comparison outputs — reproduce via ../pipelines (see comparison.md) -*.dream3d -*.xdmf diff --git a/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/results/comparison.md b/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/results/comparison.md deleted file mode 100644 index f1a5c8132e..0000000000 --- a/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/results/comparison.md +++ /dev/null @@ -1,33 +0,0 @@ -# Legacy Comparison: ConvertOrientationsFilter - -Date: 2026-06-30 - -## Test Case -Single toy orientation (Euler `(0.7853982, 0.5235988, 1.0471976)` rad = 45°/30°/60°), converted from Euler to each of the 6 other **shared** representations (Orientation Matrix, Quaternion, Axis-Angle, Rodrigues, Homochoric, Cubochoric). Stereographic excluded — no 6.5.171 equivalent (deviation D3). - -## Input Data -The legacy 6.5.171 pipeline (`pipelines/legacy_6_5_171.json`) builds the EulerAngles array from three constant scalars + `CombineAttributeArrays` and writes `results/legacy_out.dream3d`. The NX pipeline (`pipelines/nx.d3dpipeline`) **reads that same file** (NX reads legacy `.dream3d`), so the Euler input is byte-identical (verified: `np.array_equal == True`). - -## Runners -- Legacy: `/Users/mjackson/Workspace9/6.5.172/DREAM3D-Build/D3D-Rel-Qt515-6_5_171/Bin/PipelineRunner` (PipelineRunner 1.2.832, official 6.5.171) -- NX: `/Users/mjackson/Workspace9/DREAM3D-Build/NX-Com-Qt69-Vtk96-Rel/Bin/nxrunner` (1.7.0), EbsdLib 3.0.0 - -## Results — max |Δ| (legacy vs NX), per conversion - -| eu → | max \|Δ\| | -|---|---| -| Quaternion | 0 (bit-identical) | -| Axis-Angle | 0 (bit-identical) | -| Rodrigues | 0 (bit-identical) | -| Homochoric | 0 (bit-identical) | -| Orientation Matrix | 1.49e-08 | -| Cubochoric | 1.78e-06 | - -**Overall max |Δ| = 1.78e-06** (worst single component: cubochoric `cu[1]`, legacy `0.04432392` vs NX `0.04432571`). All conversions agree within 1e-5; four are bit-identical. - -## Fixes Applied -None. SIMPLNX is independently verified-correct against the Class 3 / Class 1 / Class 4 oracle; the legacy output is not wrong, so no legacy patch and no NX change. The sub-2e-6 differences are float32 round-off from library-generation drift: both legacy and NX dispatch each pair to the same direct pairwise transform (e.g. `eu2om`, `eu2cu`), but legacy links OrientationLib while NX links EbsdLib 3.x, and the two accumulate rounding differently. Largest in cubochoric, which involves a cube-root + series expansion most sensitive to intermediate precision. - -## Notes -- Confirms deviation **ConvertOrientationsFilter-D1** (order of operations + library): differences ≤ ~1.8e-6, recommendation "either acceptable within tolerance ~1e-5". -- D2 (float64 scope) not exercised here (input is float32). D3 (Stereographic) has no legacy equivalent. D4 (error codes) is a preflight-only difference. diff --git a/src/Plugins/OrientationAnalysis/vv/deviations/ComputeCAxisLocationsFilter.md b/src/Plugins/OrientationAnalysis/vv/deviations/ComputeCAxisLocationsFilter.md new file mode 100644 index 0000000000..cfc56f58e2 --- /dev/null +++ b/src/Plugins/OrientationAnalysis/vv/deviations/ComputeCAxisLocationsFilter.md @@ -0,0 +1,61 @@ +# Deviations from DREAM3D 6.5.171: ComputeCAxisLocationsFilter + +This file lists every documented behavioral difference between this SIMPLNX filter and its DREAM3D 6.5.171 equivalent. + +Entries are referenced by stable ID (`ComputeCAxisLocationsFilter-D`) from the V&V report and from public migration guidance. The ID is stable across renames; the Filter UUID field is the permanent cross-reference anchor. + +--- + +## ComputeCAxisLocationsFilter-D1 + +| Field | Value | +|---|---| +| **Deviation ID** | `ComputeCAxisLocationsFilter-D1` | +| **Filter UUID** | `a51c257a-ddc1-499a-9b21-f2d25a19d098` | +| **Status** | active | + +**Symptom:** For non-hexagonal cells, NX places NaN values whereas 6.5.171 places meaningless-but-finite computed values. + +**Root cause:** Algorithmic choice - Intentional improvement to signal to the user the that the values for those phases could not be calculated. + +**Affected users:** Anyone who has non-hexagonal phases in their input to ComputeCAxisLocationsFilter. + +**Recommendation:** Trust SIMPLNX - ComputeCAxisLocationsFilter's calculation is only correct for hexagonal cells. The legacy filter computes meaningless values for non-hexagonal phases. + +--- + +## ComputeCAxisLocationsFilter-D2 + +| Field | Value | +|---|---| +| **Deviation ID** | `ComputeCAxisLocationsFilter-D2` | +| **Filter UUID** | `a51c257a-ddc1-499a-9b21-f2d25a19d098` | +| **Status** | active | + +**Symptom:** When there are no hexagonal phases present, NX emits an error (-3522) whereas 6.5.171 executes. + +**Root cause:** Algorithmic choice - Intentional improvement to prevent running the filter on data where the filter is not valid. + +**Affected users:** Anyone who has no hexagonal phases in their input to ComputeCAxisLocationsFilter. + +**Recommendation:** Trust SIMPLNX - ComputeCAxisLocationsFilter's calculation is only correct for hexagonal cells. If there are no hexagonal cells, then the filter does no actual calculation. + +--- + +## ComputeCAxisLocationsFilter-D3 + +| Field | Value | +|---|---| +| **Deviation ID** | `ComputeCAxisLocationsFilter-D3` | +| **Filter UUID** | `a51c257a-ddc1-499a-9b21-f2d25a19d098` | +| **Status** | active | + +**Symptom:** NX emits an unconditional warning (-3521) in preflight which advises the user to make sure their data has hexagonal phases and emits a warning (-3523) if there are non-hexagonal phases. 6.5.171 does not emit any warning in either case. + +**Root cause:** Algorithmic choice - Intentional improvement to warn the user that the filter's output is only valid for hexagonal phases. + +**Affected users:** Anyone running the filter for the preflight warning, and anyone who has mixed non-hexagonal phases in their input to ComputeCAxisLocationsFilter. + +**Recommendation:** Trust SIMPLNX - ComputeCAxisLocationsFilter's calculation is only correct for hexagonal cells. + +---