diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..01c041e --- /dev/null +++ b/.clang-format @@ -0,0 +1,89 @@ +--- +Language: Cpp +# AccessModifierOffset: -2 +# AlignAfterOpenBracket: Align +# AlignConsecutiveAssignments: false +# AlignConsecutiveDeclarations: false +# AlignEscapedNewlinesLeft: false +# AlignOperands: true +# AlignTrailingComments: true +# AllowAllParametersOfDeclarationOnNextLine: true +# AllowShortBlocksOnASingleLine: false +# AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: false +# AllowShortIfStatementsOnASingleLine: false +# AllowShortLoopsOnASingleLine: false +# AlwaysBreakAfterDefinitionReturnType: None +# AlwaysBreakAfterReturnType: None +# AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +# BinPackArguments: true +# BinPackParameters: true +BraceWrapping: + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: false + AfterStruct: true + AfterUnion: false + BeforeCatch: false + BeforeElse: true + IndentBraces: false +#BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: true +ColumnLimit: 200 +# CommentPragmas: '^ IWYU pragma:' +# ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 0 +# ContinuationIndentWidth: 4 +# Cpp11BracedListStyle: true +# DerivePointerAlignment: false +# DisableFormat: false +# ExperimentalAutoDetectBinPacking: false +# ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +# IncludeCategories: +# - Regex: '^"(llvm|llvm-c|clang|clang-c)/' +# Priority: 2 +# - Regex: '^(<|"(gtest|isl|json)/)' +# Priority: 3 +# - Regex: '.*' +# Priority: 1 +# IndentCaseLabels: false +# IndentWidth: 2 +# IndentWrappedFunctionNames: false +# KeepEmptyLinesAtTheStartOfBlocks: true +# MacroBlockBegin: '' +# MacroBlockEnd: '' +# MaxEmptyLinesToKeep: 1 +# NamespaceIndentation: None +# ObjCBlockIndentWidth: 2 +# ObjCSpaceAfterProperty: false +# ObjCSpaceBeforeProtocolList: true +# PenaltyBreakBeforeFirstCallParameter: 19 +# PenaltyBreakComment: 300 +# PenaltyBreakFirstLessLess: 120 +# PenaltyBreakString: 1000 +# PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 100 +PointerAlignment: Left +# ReflowComments: true +SortIncludes: true +# SpaceAfterCStyleCast: false +# SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: Never +# SpaceInEmptyParentheses: false +# SpacesBeforeTrailingComments: 1 +# SpacesInAngles: false +# SpacesInContainerLiterals: true +# SpacesInCStyleCastParentheses: false +# SpacesInParentheses: false +# SpacesInSquareBrackets: false +SpaceAfterTemplateKeyword: true +Standard: c++20 +TabWidth: 2 +UseTab: Never +... diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..6384629 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,56 @@ +--- +Checks: "-abseil-*, + bugprone-*, + -boost-*, + -cert-*, + clang-diagnostic-*, + -clang-diagnostic-documentation, + clang-analyzer-*, + cppcoreguidelines-*, + -cppcoreguidelines-avoid-magic-numbers, + -darwin-*, + -fuchsia-*, + -google-*, + -hicpp-*, + -linuxkernel-*, + -llvm-*, + misc-*, + modernize-*, + -modernize-use-trailing-return-type, + -modernize-use-nodiscard, + -modernize-use-auto, + -mpi-*, + -objc-*, + -openmp-*, + performance-*, + -portability-*, + readability-*, + -readability-function-cognitive-complexity, + -readability-function-size, + -readability-uppercase-literal-suffix, + -readability-magic-numbers" +WarningsAsErrors: '*' +HeaderFilterRegex: 'simplnx/.*\.hpp' +FormatStyle: file +CheckOptions: + cppcoreguidelines-macro-usage.AllowedRegexp: 'SIMPLNX_EXPORT|SIMPLNX_NO_EXPORT|SIMPLNX_DEPRECATED' + readability-identifier-naming.IgnoreMainLikeFunctions: 'false' + readability-identifier-naming.PrivateMemberPrefix: 'm_' + readability-identifier-naming.NamespaceCase: lower_case + readability-identifier-naming.ClassCase: CamelCase + readability-identifier-naming.ClassMethodCase: camelBack + readability-identifier-naming.PrivateMember: CamelCase + readability-identifier-naming.PublicMemberCase: CamelCase + readability-identifier-naming.StructCase: CamelCase + readability-identifier-naming.FunctionCase: camelBack + readability-identifier-naming.VariableCase: camelBack + readability-identifier-naming.GlobalVariableCase: CamelCase + readability-identifier-naming.GlobalConstantCase: CamelCase + readability-identifier-naming.GlobalConstantPrefix: 'k_' + readability-identifier-naming.GlobalFunctionCase: CamelCase + readability-identifier-naming.LocalPointerCase: camelBack + readability-identifier-naming.LocalPointerSuffix: 'Ptr' + readability-identifier-naming.TypeAliasCase: CamelCase + readability-identifier-naming.TypeAliasSuffix: 'Type' + readability-identifier-naming.MacroDefinitionCase: UPPER_CASE +... diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..91bfadd --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +* text=auto + +*.bat text eol=crlf +*.sh text eol=lf diff --git a/.github/workflows/asan.yml b/.github/workflows/asan.yml new file mode 100644 index 0000000..9064734 --- /dev/null +++ b/.github/workflows/asan.yml @@ -0,0 +1,64 @@ +name: asan + +on: + schedule: + - cron: '15 5 * * *' + +jobs: + build: + env: + VCPKG_BINARY_SOURCES: 'clear;nuget,GitHub,readwrite' + CC: clang-17 + CXX: clang++-17 + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: true + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install sphinx myst-parser sphinx-markdown-tables sphinx_rtd_theme numpy + - name: Add C++ Problem Matcher + uses: ammaraskar/gcc-problem-matcher@0.2.0 + - name: Install Dependencies + run: | + sudo apt-get -y install ninja-build + - name: Install mono + shell: bash + run: | + sudo apt-get -y install ca-certificates gnupg + sudo gpg --homedir /tmp --no-default-keyring --keyring /usr/share/keyrings/mono-official-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF + echo "deb [signed-by=/usr/share/keyrings/mono-official-archive-keyring.gpg] https://download.mono-project.com/repo/ubuntu stable-focal main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list + sudo apt-get -y update + sudo apt-get -y install mono-complete + - name: Setup NuGet Credentials + shell: bash + run: | + mono `vcpkg fetch nuget | tail -n 1` \ + sources add \ + -source "https://nuget.pkg.github.com/BlueQuartzSoftware/index.json" \ + -storepasswordincleartext \ + -name "GitHub" \ + -username "BlueQuartzSoftware" \ + -password "${{secrets.GITHUB_TOKEN}}" + mono `vcpkg fetch nuget | tail -n 1` \ + setapikey "${{secrets.GITHUB_TOKEN}}" \ + -source "https://nuget.pkg.github.com/BlueQuartzSoftware/index.json" + - name: Configure + run: | + cmake --preset ci-asan ${{github.workspace}} + - name: Build + run: | + export LD_LIBRARY_PATH=$(dirname $($CXX -print-file-name=libclang_rt.asan-x86_64.so)) + cmake --build --preset ci-asan + - name: Test + run: | + export LD_PRELOAD=$($CXX -print-file-name=libclang_rt.asan-x86_64.so) + export LSAN_OPTIONS=suppressions=${{github.workspace}}/utilities/leak_suppressions.txt + ctest --preset ci-asan diff --git a/.github/workflows/format_pr.yml b/.github/workflows/format_pr.yml new file mode 100644 index 0000000..87966cb --- /dev/null +++ b/.github/workflows/format_pr.yml @@ -0,0 +1,33 @@ +name: clang-format pr + +on: + pull_request: + branches: + - develop + - master + +jobs: + clang_format_pr: + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 2 + - name: Add Problem Matcher + uses: ammaraskar/gcc-problem-matcher@a141586609e2a558729b99a8c574c048f7f56204 + - name: Check Formatting + id: check_format + continue-on-error: true + run: | + python3 scripts/clang_format.py --format-version 13 --commits HEAD^ HEAD + - name: Apply Formatting + if: steps.check_format.outcome != 'success' + run: | + python3 scripts/clang_format.py --format-version 13 --modify --commits HEAD^ HEAD + - name: Add Suggestions + if: steps.check_format.outcome != 'success' + uses: reviewdog/action-suggester@v1 + with: + tool_name: clang-format + fail_level: error diff --git a/.github/workflows/format_push.yml b/.github/workflows/format_push.yml new file mode 100644 index 0000000..a4a6497 --- /dev/null +++ b/.github/workflows/format_push.yml @@ -0,0 +1,19 @@ +name: clang-format + +on: + push: + branches: + - develop + - master + +jobs: + clang_format: + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v2 + - name: Add Problem Matcher + uses: ammaraskar/gcc-problem-matcher@a141586609e2a558729b99a8c574c048f7f56204 + - name: Check Formatting + run: | + python3 scripts/clang_format.py --format-version 13 diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml new file mode 100644 index 0000000..23eabca --- /dev/null +++ b/.github/workflows/linux.yml @@ -0,0 +1,76 @@ +name: linux + +on: + pull_request: + branches: + - develop + - master + push: + branches: + - develop + - master + +jobs: + build: + env: + VCPKG_BINARY_SOURCES: 'clear;nuget,GitHub,readwrite' + strategy: + fail-fast: false + matrix: + os: + - ubuntu-22.04 + cxx: + - g++-11 + - clang++-14 + include: + - cxx: g++-11 + cc: gcc-11 + - cxx: clang++-14 + cc: clang-14 + runs-on: ${{matrix.os}} + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: true + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install sphinx myst-parser sphinx-markdown-tables sphinx_rtd_theme numpy + - name: Add C++ Problem Matcher + uses: ammaraskar/gcc-problem-matcher@0.2.0 + - name: Install Dependencies - 2 + run: | + sudo apt-get -y install ninja-build + - name: Install Sphinx + run: | + sudo pip3 install sphinx myst-parser sphinx-markdown-tables sphinx_rtd_theme numpy + - name: Setup NuGet Credentials + shell: bash + run: | + mono `vcpkg fetch nuget | tail -n 1` \ + sources add \ + -source "https://nuget.pkg.github.com/BlueQuartzSoftware/index.json" \ + -storepasswordincleartext \ + -name "GitHub" \ + -username "BlueQuartzSoftware" \ + -password "${{secrets.GITHUB_TOKEN}}" + mono `vcpkg fetch nuget | tail -n 1` \ + setapikey "${{secrets.GITHUB_TOKEN}}" \ + -source "https://nuget.pkg.github.com/BlueQuartzSoftware/index.json" + - name: Configure + env: + CC: ${{matrix.cc}} + CXX: ${{matrix.cxx}} + run: | + cmake --preset ci-linux-x64 ${{github.workspace}} + - name: Build + run: | + cmake --build --preset ci-linux-x64 + - name: Test + run: | + ctest --preset ci-linux-x64 diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml new file mode 100644 index 0000000..d8db316 --- /dev/null +++ b/.github/workflows/macos.yml @@ -0,0 +1,78 @@ +name: macos + +on: + pull_request: + branches: + - develop + - master + push: + branches: + - develop + - master + +jobs: + build: + env: + VCPKG_BINARY_SOURCES: 'clear;nuget,GitHub,readwrite' + strategy: + fail-fast: false + matrix: + os: + - macos-14 + - macos-15 + include: + - os: macos-14 + preset: ci-macos-arm64 + - os: macos-15 + preset: ci-macos-arm64 + runs-on: ${{matrix.os}} + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: true + - name: Checkout vcpkg + run: | + git clone https://www.github.com/microsoft/vcpkg && cd vcpkg && ./bootstrap-vcpkg.sh + VCPKG_INSTALLATION_ROOT=${{github.workspace}}/vcpkg + echo "$VCPKG_INSTALLATION_ROOT" >> $GITHUB_PATH + echo "VCPKG_INSTALLATION_ROOT=$VCPKG_INSTALLATION_ROOT" >> "$GITHUB_ENV" + if: matrix.os == 'macos-14' + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install sphinx myst-parser sphinx-markdown-tables sphinx_rtd_theme numpy + - name: Add C++ Problem Matcher + uses: ammaraskar/gcc-problem-matcher@0.2.0 + - name: Install Dependencies + run: | + brew install ninja mono + - name: Install Sphinx + run: | + sudo pip3 install sphinx myst-parser sphinx-markdown-tables sphinx_rtd_theme numpy + - name: Setup NuGet Credentials + shell: bash + run: | + mono `vcpkg fetch nuget | tail -n 1` \ + sources add \ + -source "https://nuget.pkg.github.com/BlueQuartzSoftware/index.json" \ + -storepasswordincleartext \ + -name "GitHub" \ + -username "BlueQuartzSoftware" \ + -password "${{secrets.GITHUB_TOKEN}}" + mono `vcpkg fetch nuget | tail -n 1` \ + setapikey "${{secrets.GITHUB_TOKEN}}" \ + -source "https://nuget.pkg.github.com/BlueQuartzSoftware/index.json" + - name: Configure + run: | + cmake --preset ${{matrix.preset}} ${{github.workspace}} + - name: Build + run: | + cmake --build --preset ${{matrix.preset}} + - name: Test + run: | + ctest --preset ${{matrix.preset}} diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml new file mode 100644 index 0000000..53ee3b4 --- /dev/null +++ b/.github/workflows/windows.yml @@ -0,0 +1,57 @@ +name: windows + +on: + pull_request: + branches: + - develop + push: + branches: + - develop +jobs: + build: + env: + VCPKG_BINARY_SOURCES: 'clear;nuget,GitHub,readwrite' + strategy: + fail-fast: false + matrix: + os: + - windows-2022 + toolset: + - v143 + runs-on: ${{matrix.os}} + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: true + - name: Add C++ Problem Matcher + uses: ammaraskar/msvc-problem-matcher@0.2.0 + - name: Setup Build Environment + uses: ilammy/msvc-dev-cmd@v1.12.1 + with: + vsversion: 2022 + - name: Setup NuGet Credentials + shell: bash + run: | + `vcpkg fetch nuget | tail -n 1` \ + sources add \ + -source "https://nuget.pkg.github.com/BlueQuartzSoftware/index.json" \ + -storepasswordincleartext \ + -name "GitHub" \ + -username "BlueQuartzSoftware" \ + -password "${{secrets.GITHUB_TOKEN}}" + `vcpkg fetch nuget | tail -n 1` \ + setapikey "${{secrets.GITHUB_TOKEN}}" \ + -source "https://nuget.pkg.github.com/BlueQuartzSoftware/index.json" + - name: Install Sphinx + run: | + pip install sphinx myst-parser sphinx-markdown-tables sphinx_rtd_theme numpy + - name: Configure + run: | + cmake --preset ci-windows-${{matrix.toolset}} ${{github.workspace}} -T ${{matrix.toolset}} + - name: Build + run: | + cmake --build --preset ci-windows-${{matrix.toolset}} + - name: Test + run: | + ctest --preset ci-windows-${{matrix.toolset}} diff --git a/.gitignore b/.gitignore index 105a091..6a58a02 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,34 @@ -/.claude +__pycache__/ +.cproject +.ctags +.floo +.idea +.idea/ +.project +.vscode +.vscode/ +*.user* +/[Bb]uild/ +/[Bb]uilds/ +/[Dd]ebug/ +/[Rr]elease/ +/Build-ninja/ +/Build-Xcode/ +/Build2/ +/clang/ +/i386/ +/linux/ +/ninja/ +/QT_BUILD/.settings +/qtcreator/ +/x64/ +/xcode/ +CMakeLists.txt.* +CMakeUserPresets.json +dist/ +DREAM3D_Data +test_data/ +vcpkg_installed/ +Workspace CLAUDE.md -*.mat +.claude/ diff --git a/CMakeLists.txt b/CMakeLists.txt index f9612ca..f677769 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,15 +51,16 @@ find_package(CLI11 CONFIG REQUIRED) find_package(nlohmann_json CONFIG REQUIRED) find_package(spdlog CONFIG REQUIRED) find_package(Stb REQUIRED) + +# ----------------------------------------------------------------------- +# Find HDF5 and get the path to the DLL libraries and put that into a +# global property for later install, debugging and packaging +# ----------------------------------------------------------------------- find_package(HDF5 1.14 MODULE REQUIRED) get_target_property(hdf5_dll_path hdf5::hdf5 IMPORTED_LOCATION_RELEASE) get_filename_component(hdf5_dll_path "${hdf5_dll_path}" DIRECTORY) -get_property(MTRSIM_EXTRA_LIBRARY_DIRS GLOBAL PROPERTY MTRSIM_EXTRA_LIBRARY_DIRS) -set_property(GLOBAL PROPERTY MTRSIM_EXTRA_LIBRARY_DIRS ${MTRSIM_EXTRA_LIBRARY_DIRS} ${hdf5_dll_path}) - -# if(NOT TARGET H5Support::H5Support) -# add_subdirectory("${MTRSim_SOURCE_DIR}/../H5Support" "${MTRSim_BINARY_DIR}/H5Support") -# endif() +get_property(MTRSim_EXTRA_LIBRARY_DIRS GLOBAL PROPERTY MTRSim_EXTRA_LIBRARY_DIRS) +set_property(GLOBAL PROPERTY MTRSim_EXTRA_LIBRARY_DIRS ${MTRSim_EXTRA_LIBRARY_DIRS} ${hdf5_dll_path}) # ----------------------------------------------------------------------- # Find oneTBB and get the path to the DLL libraries and put that into a @@ -73,22 +74,8 @@ if(MTRSIM_ENABLE_MULTICORE) set_property(GLOBAL PROPERTY MTRSIM_EXTRA_LIBRARY_DIRS ${MTRSIM_EXTRA_LIBRARY_DIRS} ${tbb_dll_path}) endif() - -if(NOT TARGET EbsdLib::EbsdLib) - if(EXISTS "${MTRSim_SOURCE_DIR}/../EbsdLib") - set(EbsdLibProj_SOURCE_DIR "${MTRSim_SOURCE_DIR}/../EbsdLib") - else() - message(FATAL_ERROR "EbsdLibProj_SOURCE_DIR was not set. Where is the EbsdLib project directory. Please set the EbsdLibProj_SOURCE_DIR variable to the EbsdLib directory.") - endif() - message(STATUS "EbsdLibProj_SOURCE_DIR: ${EbsdLibProj_SOURCE_DIR}") - - set(EbsdLib_ENABLE_HDF5 OFF) - set(EbsdLib_USE_PARALLEL_ALGORITHMS ${MTRSIM_ENABLE_MULTICORE}) - set(EbsdLib_BUILD_H5SUPPORT OFF) - set(H5Support_INCLUDE_QT_API OFF) - set(EbsdLib_BUILD_TESTS OFF) - add_subdirectory( ${EbsdLibProj_SOURCE_DIR} ${PROJECT_BINARY_DIR}/EbsdLib) -endif() +find_package(H5Support REQUIRED) +find_package(EbsdLib REQUIRED) if(MTRSIM_BUILD_TESTS) find_package(Catch2 CONFIG REQUIRED) diff --git a/CMakePresets.json b/CMakePresets.json index b938ed6..dc99c64 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -27,49 +27,248 @@ }, "VCPKG_MANIFEST_FEATURES": { "type": "STRING", - "value": "tests" + "value": "tests;parallel" + }, + "VCPKG_OVERLAY_TRIPLETS": { + "type": "PATH", + "value": "${sourceDir}/cmake/triplets" + } + } + }, + { + "name": "ci-asan", + "inherits": "ci", + "cacheVariables": { + "CMAKE_BUILD_TYPE": { + "type": "STRING", + "value": "Debug" + }, + "CMAKE_CXX_FLAGS": { + "type": "STRING", + "value": "-O1 -fsanitize=address -fno-omit-frame-pointer -shared-libasan" + }, + "VCPKG_TARGET_TRIPLET": { + "type": "STRING", + "value": "x64-linux-dynamic" + }, + "VCPKG_HOST_TRIPLET": { + "type": "STRING", + "value": "x64-linux-dynamic" + }, + "SIMPLNX_BUILD_PYTHON_DOCS": { + "type": "BOOL", + "value": "OFF" + } + } + }, + { + "name": "ci-windows-v142", + "displayName": "ci-windows-v142", + "description": "Build configuration for GitHub Actions CI", + "generator": "Visual Studio 17 2022", + "inherits": "ci", + "cacheVariables": { + "VCPKG_TARGET_TRIPLET": { + "type": "STRING", + "value": "x64-windows-v142" + }, + "VCPKG_HOST_TRIPLET": { + "type": "STRING", + "value": "x64-windows-v142" + } + } + }, + { + "name": "ci-windows-v143", + "displayName": "ci-windows-v143", + "description": "Build configuration for GitHub Actions CI", + "generator": "Visual Studio 17 2022", + "inherits": "ci", + "cacheVariables": { + "VCPKG_TARGET_TRIPLET": { + "type": "STRING", + "value": "x64-windows-v143" + }, + "VCPKG_HOST_TRIPLET": { + "type": "STRING", + "value": "x64-windows-v143" + } + } + }, + { + "name": "ci-macos-x64", + "displayName": "ci-macos-x64", + "description": "Build configuration for GitHub Actions CI", + "generator": "Ninja", + "inherits": "ci", + "cacheVariables": { + "VCPKG_TARGET_TRIPLET": { + "type": "STRING", + "value": "x64-osx-v11" + }, + "VCPKG_HOST_TRIPLET": { + "type": "STRING", + "value": "x64-osx-v11" + }, + "Python3_EXECUTABLE": { + "type": "PATH", + "value": "/Users/runner/hostedtoolcache/Python/3.10.13/x64/bin/python3.10" + }, + "SIMPLNX_PY_DISABLE_HIDDEN_VISIBILITY": { + "type": "BOOL", + "value": "ON" } } }, { "name": "ci-macos-arm64", - "displayName": "CI macOS arm64", - "description": "CI build configuration for macOS Apple Silicon", + "displayName": "ci-macos-arm64", + "description": "Build configuration for GitHub Actions CI", + "generator": "Ninja", "inherits": "ci", + "cacheVariables": { + "VCPKG_TARGET_TRIPLET": { + "type": "STRING", + "value": "arm64-osx-dynamic" + }, + "VCPKG_HOST_TRIPLET": { + "type": "STRING", + "value": "arm64-osx-dynamic" + }, + "Python3_EXECUTABLE": { + "type": "PATH", + "value": "/Users/runner/hostedtoolcache/Python/3.10.13/x64/bin/python3.10" + }, + "SIMPLNX_PY_DISABLE_HIDDEN_VISIBILITY": { + "type": "BOOL", + "value": "ON" + } + } + }, + { + "name": "ci-linux-x64", + "displayName": "ci-linux-x64", + "description": "Build configuration for GitHub Actions CI", "generator": "Ninja", + "inherits": "ci", "cacheVariables": { "VCPKG_TARGET_TRIPLET": { "type": "STRING", - "value": "arm64-osx-v11" + "value": "x64-linux-dynamic" }, "VCPKG_HOST_TRIPLET": { "type": "STRING", - "value": "arm64-osx-v11" + "value": "x64-linux-dynamic" }, - "VCPKG_OVERLAY_TRIPLETS": { + "Python3_EXECUTABLE": { "type": "PATH", - "value": "${sourceDir}/cmake/triplets" + "value": "/opt/hostedtoolcache/Python/3.10.13/x64/bin/python3.10" } } } ], "buildPresets": [ + { + "name": "ci-windows-v142", + "displayName": "ci-windows-v142 Release build", + "description": "Build configuration for GitHub actions CI", + "configurePreset": "ci-windows-v142", + "configuration": "Release" + }, + { + "name": "ci-windows-v143", + "displayName": "ci-windows-v143 CI build", + "description": "Build configuration for GitHub actions CI", + "configurePreset": "ci-windows-v143", + "configuration": "Release" + }, + { + "name": "ci-macos-x64", + "displayName": "ci-macos-x64 CI build", + "description": "Build configuration for GitHub actions CI", + "configurePreset": "ci-macos-x64", + "configuration": "Release" + }, { "name": "ci-macos-arm64", - "displayName": "CI macOS arm64 build", + "displayName": "ci-macos-arm64 CI build", + "description": "Build configuration for GitHub actions CI", "configurePreset": "ci-macos-arm64", "configuration": "Release" + }, + { + "name": "ci-linux-x64", + "displayName": "ci-linux-x64 CI build", + "description": "Build configuration for GitHub actions CI", + "configurePreset": "ci-linux-x64", + "configuration": "Release" + }, + { + "name": "ci-asan", + "displayName": "asan CI build", + "description": "Build configuration for GitHub actions CI", + "configurePreset": "ci-asan" } ], "testPresets": [ + { + "name": "ci-windows-v142", + "displayName": "ci-windows-v142 CI build", + "description": "Build configuration for GitHub actions CI", + "configurePreset": "ci-windows-v142", + "configuration": "Release", + "output": { + "outputOnFailure": true + } + }, + { + "name": "ci-windows-v143", + "displayName": "ci-windows-v143 CI build", + "description": "Build configuration for GitHub actions CI", + "configurePreset": "ci-windows-v143", + "configuration": "Release", + "output": { + "outputOnFailure": true + } + }, + { + "name": "ci-macos-x64", + "displayName": "ci-macos-x64 CI build", + "description": "Build configuration for GitHub actions CI", + "configurePreset": "ci-macos-x64", + "configuration": "Release", + "output": { + "outputOnFailure": true + } + }, { "name": "ci-macos-arm64", - "displayName": "CI macOS arm64 test", + "displayName": "ci-macos-arm64 CI build", + "description": "Build configuration for GitHub actions CI", "configurePreset": "ci-macos-arm64", "configuration": "Release", "output": { "outputOnFailure": true } + }, + { + "name": "ci-linux-x64", + "displayName": "ci-linux-x64 CI build", + "description": "Build configuration for GitHub actions CI", + "configurePreset": "ci-linux-x64", + "configuration": "Release", + "output": { + "outputOnFailure": true + } + }, + { + "name": "ci-asan", + "displayName": "asan CI build", + "description": "Build configuration for GitHub actions CI", + "configurePreset": "ci-asan", + "output": { + "outputOnFailure": true + } } ] } diff --git a/CMakeUserPresets.json b/CMakeUserPresets.json deleted file mode 100644 index 155b793..0000000 --- a/CMakeUserPresets.json +++ /dev/null @@ -1,112 +0,0 @@ -{ - "version": 3, - "cmakeMinimumRequired": { - "major": 3, - "minor": 24, - "patch": 0 - }, - "configurePresets": [ - { - "name": "default-all", - "displayName": "default-all", - "description": "Base configuration for local macOS arm64 developer builds", - "generator": "Ninja", - "hidden": true, - "cacheVariables": { - "VCPKG_MANIFEST_DIR": { - "type": "STRING", - "value": "${sourceDir}" - }, - "VCPKG_INSTALLED_DIR": { - "type": "STRING", - "value": "${sourceDir}/../vcpkg-installed" - }, - "VCPKG_MANIFEST_INSTALL": { - "type": "BOOL", - "value": "ON" - }, - "VCPKG_OVERLAY_TRIPLETS": { - "type": "PATH", - "value": "${sourceDir}/cmake/triplets" - }, - "VCPKG_APPLOCAL_DEPS": { - "type": "BOOL", - "value": "OFF" - }, - "VCPKG_MANIFEST_FEATURES": { - "type": "STRING", - "value": "tests;parallel" - }, - "CMAKE_TOOLCHAIN_FILE": { - "type": "FILEPATH", - "value": "$env{VCPKG_INSTALLATION_ROOT}/scripts/buildsystems/vcpkg.cmake" - }, - "CMAKE_MAKE_PROGRAM": { - "type": "FILEPATH", - "value": "/opt/local/bin/ninja" - }, - "CMAKE_OSX_DEPLOYMENT_TARGET": { - "type": "STRING", - "value": "11.0" - }, - "MTRSIM_BUILD_TESTS": { - "type": "BOOL", - "value": "ON" - }, - "MTRSIM_ENABLE_MULTICORE": { - "type": "BOOL", - "value": "ON" - }, - "EbsdLib_BUILD_TESTS": { - "type": "BOOL", - "value": "OFF" - } - }, - "environment": { - "VCPKG_INSTALLATION_ROOT": "/opt/local/vcpkg" - } - }, - { - "name": "mtrsim-Rel", - "inherits": "default-all", - "displayName": "mtrsim-Rel", - "description": "Release build for local macOS arm64", - "generator": "Ninja", - "binaryDir": "${sourceDir}/../Build/mtrsim-Rel", - "cacheVariables": { - "CMAKE_BUILD_TYPE": { - "type": "STRING", - "value": "Release" - } - } - }, - { - "name": "mtrsim-Dbg", - "inherits": "mtrsim-Rel", - "displayName": "mtrsim-Dbg", - "description": "Debug build for local macOS arm64", - "binaryDir": "${sourceDir}/../Build/mtrsim-Dbg", - "cacheVariables": { - "CMAKE_BUILD_TYPE": { - "type": "STRING", - "value": "Debug" - } - } - } - ], - "buildPresets": [ - { - "name": "mtrsim-Rel [BUILD]", - "displayName": "mtrsim-Rel", - "configurePreset": "mtrsim-Rel", - "configuration": "Release" - }, - { - "name": "mtrsim-Dbg [BUILD]", - "displayName": "mtrsim-Dbg", - "configurePreset": "mtrsim-Dbg", - "configuration": "Debug" - } - ], - "testPresets": [] -} diff --git a/app/main.cpp b/app/main.cpp index 56ce282..a327822 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -48,7 +48,7 @@ std::vector readH5Vector(hid_t fileId, const std::string& path) } const hid_t spaceId = H5Dget_space(dsId); - hsize_t dims[1] = {0}; + hsize_t dims[1] = {0}; H5Sget_simple_extent_dims(spaceId, dims, nullptr); H5Sclose(spaceId); @@ -85,15 +85,15 @@ std::vector loadODFComponents(const std::string& hdfPath) { const std::string prefix = "/ODF_best/component_" + std::to_string(j); - auto odfVec = readH5Vector(fileId, prefix + "/ODFval"); + auto odfVec = readH5Vector(fileId, prefix + "/ODFval"); auto phi1Vec = readH5Vector(fileId, prefix + "/phi1_bins"); - auto phiVec = readH5Vector(fileId, prefix + "/PHI_bins"); + auto phiVec = readH5Vector(fileId, prefix + "/PHI_bins"); auto phi2Vec = readH5Vector(fileId, prefix + "/phi2_bins"); mtrsim::ODFComponent comp; - comp.odfVal = Eigen::Map(odfVec.data(), static_cast(odfVec.size())); + comp.odfVal = Eigen::Map(odfVec.data(), static_cast(odfVec.size())); comp.phi1Bins = Eigen::Map(phi1Vec.data(), static_cast(phi1Vec.size())); - comp.phiBins = Eigen::Map(phiVec.data(), static_cast(phiVec.size())); + comp.phiBins = Eigen::Map(phiVec.data(), static_cast(phiVec.size())); comp.phi2Bins = Eigen::Map(phi2Vec.data(), static_cast(phi2Vec.size())); // Normalise so that odfVal sums to 1 (matches MATLAB pre-processing in simulate_MTRs.m) @@ -118,13 +118,13 @@ std::vector loadODFComponents(const std::string& hdfPath) // where ix = i1*(36*72) + iPHI*72 + i2. mtrsim::ODFComponent buildUniformODF() { - constexpr int nBins1 = 72; + constexpr int nBins1 = 72; constexpr int nBinsPHI = 36; - constexpr int nBins2 = 72; - constexpr int nTotal = nBins1 * nBinsPHI * nBins2; // 186624 + constexpr int nBins2 = 72; + constexpr int nTotal = nBins1 * nBinsPHI * nBins2; // 186624 const double k_TwoPiOver = 2.0 * std::numbers::pi / static_cast(nBins1); - const double k_PiOver = std::numbers::pi / static_cast(nBinsPHI); + const double k_PiOver = std::numbers::pi / static_cast(nBinsPHI); Eigen::VectorXd phi1Bins(nTotal); Eigen::VectorXd phiBins(nTotal); @@ -132,18 +132,18 @@ mtrsim::ODFComponent buildUniformODF() for(int ix = 0; ix < nTotal; ++ix) { - const int i1 = ix / (nBinsPHI * nBins2); + const int i1 = ix / (nBinsPHI * nBins2); const int iPHI = (ix % (nBinsPHI * nBins2)) / nBins2; - const int i2 = ix % nBins2; - phi1Bins[ix] = (i1 + 0.5) * k_TwoPiOver; - phiBins[ix] = (iPHI + 0.5) * k_PiOver; - phi2Bins[ix] = (i2 + 0.5) * k_TwoPiOver; + const int i2 = ix % nBins2; + phi1Bins[ix] = (i1 + 0.5) * k_TwoPiOver; + phiBins[ix] = (iPHI + 0.5) * k_PiOver; + phi2Bins[ix] = (i2 + 0.5) * k_TwoPiOver; } mtrsim::ODFComponent uni; - uni.odfVal = Eigen::VectorXd::Constant(nTotal, 1.0 / static_cast(nTotal)); + uni.odfVal = Eigen::VectorXd::Constant(nTotal, 1.0 / static_cast(nTotal)); uni.phi1Bins = std::move(phi1Bins); - uni.phiBins = std::move(phiBins); + uni.phiBins = std::move(phiBins); uni.phi2Bins = std::move(phi2Bins); return uni; } @@ -173,7 +173,7 @@ int main(int argc, char** argv) // ── Parse JSON config ──────────────────────────────────────────────────────── mtrsim::SimulationParams params; params.outputDir = outputDir; - params.seed = seed; // may be overridden by JSON (unless CLI --seed was set) + params.seed = seed; // may be overridden by JSON (unless CLI --seed was set) if(!configPath.empty()) { @@ -187,23 +187,32 @@ int main(int argc, char** argv) try { const nlohmann::json j = nlohmann::json::parse(f); - if(j.contains("xLen")) params.xLen = j["xLen"].get(); - if(j.contains("yLen")) params.yLen = j["yLen"].get(); - if(j.contains("zLen")) params.zLen = j["zLen"].get(); - if(j.contains("dx")) params.dx = j["dx"].get(); - if(j.contains("dy")) params.dy = j["dy"].get(); - if(j.contains("dz")) params.dz = j["dz"].get(); - if(j.contains("volumeFractions")) params.volumeFractions = j["volumeFractions"].get>(); - if(j.contains("thetaList")) params.thetaList = j["thetaList"].get>>(); - if(j.contains("nuggetVariance")) params.nuggetVariance = j["nuggetVariance"].get>(); - if(j.contains("odfInputPath")) params.odfInputPath = j["odfInputPath"].get(); + if(j.contains("xLen")) + params.xLen = j["xLen"].get(); + if(j.contains("yLen")) + params.yLen = j["yLen"].get(); + if(j.contains("zLen")) + params.zLen = j["zLen"].get(); + if(j.contains("dx")) + params.dx = j["dx"].get(); + if(j.contains("dy")) + params.dy = j["dy"].get(); + if(j.contains("dz")) + params.dz = j["dz"].get(); + if(j.contains("volumeFractions")) + params.volumeFractions = j["volumeFractions"].get>(); + if(j.contains("thetaList")) + params.thetaList = j["thetaList"].get>>(); + if(j.contains("nuggetVariance")) + params.nuggetVariance = j["nuggetVariance"].get>(); + if(j.contains("odfInputPath")) + params.odfInputPath = j["odfInputPath"].get(); // JSON seed only applies when CLI --seed was not explicitly provided (seed == 0) if(j.contains("seed") && params.seed == 0) { params.seed = j["seed"].get(); } - } - catch(const nlohmann::json::exception& e) + } catch(const nlohmann::json::exception& e) { spdlog::error("JSON parse error: {}", e.what()); return 1; @@ -228,7 +237,7 @@ int main(int argc, char** argv) const int nx = static_cast(std::round(params.xLen / params.dx)); const int ny = static_cast(std::round(params.yLen / params.dy)); const int nz = std::max(static_cast(std::round(params.zLen / params.dz)), 1); - const int N = nx * ny * nz; + const int N = nx * ny * nz; spdlog::info("Grid: nx={} ny={} nz={} N={}", nx, ny, nz, N); spdlog::info(" xLen={:.3f} mm yLen={:.3f} mm zLen={:.3f} mm", params.xLen, params.yLen, params.zLen); @@ -267,8 +276,7 @@ int main(int argc, char** argv) try { odfComponents = loadODFComponents(params.odfInputPath); - } - catch(const std::exception& e) + } catch(const std::exception& e) { spdlog::error("ODF load failed: {}", e.what()); return 1; @@ -293,8 +301,7 @@ int main(int argc, char** argv) for(int j = 0; j < numComponents; ++j) { spdlog::info(" Component {} / {}", j + 1, numComponents); - orientSamples[static_cast(j)] = - sampler.sampleN(N, odfComponents[static_cast(j)], uniformOdf); + orientSamples[static_cast(j)] = sampler.sampleN(N, odfComponents[static_cast(j)], uniformOdf); } spdlog::info("Orientation sampling complete."); @@ -308,7 +315,7 @@ int main(int argc, char** argv) { const int comp = result.mtrIndex[i] - 1; phi1Vec[i] = orientSamples[static_cast(comp)](i, 0); - phiVec[i] = orientSamples[static_cast(comp)](i, 1); + phiVec[i] = orientSamples[static_cast(comp)](i, 1); phi2Vec[i] = orientSamples[static_cast(comp)](i, 2); } @@ -321,8 +328,7 @@ int main(int argc, char** argv) const Eigen::MatrixXd coords2D = spatialCoords.leftCols(2); mapper.writePNG(coords2D, phi1Vec, phiVec, phi2Vec, ipfPath); spdlog::info("IPF map written."); - } - catch(const std::exception& e) + } catch(const std::exception& e) { spdlog::warn("IPF map write failed: {}", e.what()); } @@ -344,13 +350,7 @@ int main(int argc, char** argv) for(int i = 0; i < N; ++i) { - csv << spatialCoords(i, 0) << ',' - << spatialCoords(i, 1) << ',' - << spatialCoords(i, 2) << ',' - << phi1Vec[i] << ',' - << phiVec[i] << ',' - << phi2Vec[i] << ',' - << result.mtrIndex[i] << '\n'; + csv << spatialCoords(i, 0) << ',' << spatialCoords(i, 1) << ',' << spatialCoords(i, 2) << ',' << phi1Vec[i] << ',' << phiVec[i] << ',' << phi2Vec[i] << ',' << result.mtrIndex[i] << '\n'; } } spdlog::info("Results CSV written."); diff --git a/cmake/UseEbsdLib.cmake b/cmake/UseEbsdLib.cmake new file mode 100644 index 0000000..3d4cdde --- /dev/null +++ b/cmake/UseEbsdLib.cmake @@ -0,0 +1,26 @@ +# ------------------------------------------------------------------------------ +# Required EbsdLib and H5Support +# ------------------------------------------------------------------------------ + +if(MTRSIM_BUILD_EBSDLIB) + find_package(H5Support REQUIRED) + find_package(EbsdLib REQUIRED) +else() + + if(NOT TARGET EbsdLib::EbsdLib) + + if(EXISTS "${MTRSim_SOURCE_DIR}/../EbsdLib") + set(EbsdLibProj_SOURCE_DIR "${MTRSim_SOURCE_DIR}/../EbsdLib") + else() + message(FATAL_ERROR "EbsdLibProj_SOURCE_DIR was not set. Where is the EbsdLib project directory. Please set the EbsdLibProj_SOURCE_DIR variable to the EbsdLib directory.") + endif() + message(STATUS "EbsdLibProj_SOURCE_DIR: ${EbsdLibProj_SOURCE_DIR}") + + set(EbsdLib_ENABLE_HDF5 ON) + set(EbsdLib_USE_PARALLEL_ALGORITHMS ${MTRSIM_ENABLE_MULTICORE}) + set(EbsdLib_BUILD_H5SUPPORT ON) + set(H5Support_INCLUDE_QT_API OFF) + add_subdirectory( ${EbsdLibProj_SOURCE_DIR} ${PROJECT_BINARY_DIR}/EbsdLib) + endif() +endif() + diff --git a/cmake/triplets/x64-osx-v11.cmake b/cmake/triplets/x64-osx-v11.cmake new file mode 100644 index 0000000..5cfbf54 --- /dev/null +++ b/cmake/triplets/x64-osx-v11.cmake @@ -0,0 +1,7 @@ +set(VCPKG_TARGET_ARCHITECTURE x64) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE dynamic) + +set(VCPKG_CMAKE_SYSTEM_NAME Darwin) +set(VCPKG_OSX_ARCHITECTURES x86_64) +set(VCPKG_OSX_DEPLOYMENT_TARGET 11.0) diff --git a/cmake/triplets/x64-windows-v142.cmake b/cmake/triplets/x64-windows-v142.cmake new file mode 100644 index 0000000..638b5d5 --- /dev/null +++ b/cmake/triplets/x64-windows-v142.cmake @@ -0,0 +1,4 @@ +set(VCPKG_TARGET_ARCHITECTURE x64) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE dynamic) +set(VCPKG_PLATFORM_TOOLSET "v142") diff --git a/cmake/triplets/x64-windows-v143.cmake b/cmake/triplets/x64-windows-v143.cmake new file mode 100644 index 0000000..0924eea --- /dev/null +++ b/cmake/triplets/x64-windows-v143.cmake @@ -0,0 +1,4 @@ +set(VCPKG_TARGET_ARCHITECTURE x64) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE dynamic) +set(VCPKG_PLATFORM_TOOLSET v143) diff --git a/data/PF_coords_for_ODF.mat b/data/PF_coords_for_ODF.mat new file mode 100644 index 0000000..2f713dc Binary files /dev/null and b/data/PF_coords_for_ODF.mat differ diff --git a/data/PF_indexed.mat b/data/PF_indexed.mat new file mode 100644 index 0000000..f3bc774 Binary files /dev/null and b/data/PF_indexed.mat differ diff --git a/data/blank_ODF.mat b/data/blank_ODF.mat new file mode 100644 index 0000000..4362f21 Binary files /dev/null and b/data/blank_ODF.mat differ diff --git a/data/simulation_ODF.mat b/data/simulation_ODF.mat new file mode 100644 index 0000000..d69c38f Binary files /dev/null and b/data/simulation_ODF.mat differ diff --git a/data/uniformODF.mat b/data/uniformODF.mat new file mode 100644 index 0000000..680014d Binary files /dev/null and b/data/uniformODF.mat differ diff --git a/data/uniformPF.mat b/data/uniformPF.mat new file mode 100644 index 0000000..26d659f Binary files /dev/null and b/data/uniformPF.mat differ diff --git a/scripts/clang_format.py b/scripts/clang_format.py new file mode 100644 index 0000000..eea568d --- /dev/null +++ b/scripts/clang_format.py @@ -0,0 +1,121 @@ +import argparse +import re +import subprocess + +from typing import List, Optional, Tuple + +EXE = 'clang-format' +FILE_TYPES = ( + '.h', + '.H', + '.hpp', + '.hh', + '.h++', + '.hxx', + '.c', + '.C', + '.cpp', + '.cc', + '.c++', + '.cxx', +) + +def filter_files(files: List[str]) -> List[str]: + return [file for file in files if file.endswith(FILE_TYPES)] + +def get_git_files() -> List[str]: + try: + result = subprocess.run(['git', 'ls-files'], text=True, capture_output=True, check=True) + except subprocess.CalledProcessError as error: + print(error.stderr) + raise + git_files = result.stdout.splitlines() + return filter_files(git_files) + +def get_git_diff_files(previous_commit: str, current_commit: str) -> List[str]: + try: + result = subprocess.run( + ['git', 'diff', '--name-only', '--diff-filter=d', f'{previous_commit}...{current_commit}'], + text=True, + capture_output=True, + check=True + ) + except subprocess.CalledProcessError as error: + print(error.stderr) + raise + git_files = result.stdout.splitlines() + return filter_files(git_files) + +def format(file: str, should_modify: bool, version: Optional[int] = None) -> int: + exe = EXE + if version: + exe = f'{EXE}-{version}' + command = [exe, '--style=file'] + if should_modify: + command.append('-i') + else: + command.extend(['--dry-run', '--Werror']) + command.append(file) + result = subprocess.run(command, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + for line in result.stdout.splitlines(): + print(line, flush=True) + return result.returncode + +def get_clang_format_version() -> Tuple[int, int, int]: + try: + version_result = subprocess.run([EXE, '--version'], text=True, capture_output=True, check=True) + except subprocess.CalledProcessError as error: + print(error.stderr) + raise + version_string = version_result.stdout + match = re.match(r'clang-format version (\d+)\.(\d+)\.(\d+)', version_string) + major = int(match.group(1)) + minor = int(match.group(2)) + patch = int(match.group(3)) + return (major, minor, patch) + +def main() -> int: + parser = argparse.ArgumentParser(description='Run clang-format on files in a git repo') + parser.add_argument('--modify', help='Apply clang-format modifications in place', action='store_true') + parser.add_argument('--commits', help='Check formatting of diff between two commits instead of all files (previous, current)', nargs=2) + parser.add_argument('--format-version', help='clang-format version', type=int) + + args = parser.parse_args() + + # clang-format must be at least major version 10 since that's when --dry-run and --Werror were introduced + if args.format_version: + if args.format_version < 10: + parser.error('format-version must be at least 10') + else: + version = get_clang_format_version() + if version[0] < 10: + raise RuntimeError('clang-format must be at least major version 10') + + if args.commits: + files = get_git_diff_files(args.commits[0], args.commits[1]) + else: + files = get_git_files() + + if not files: + print('No files to format') + + failed_files: List[str] = [] + + result = 0 + for file in files: + format_result = format(file, args.modify, args.format_version) + if format_result != 0: + failed_files.append(file) + result = 1 + + if result == 0: + print('All files were formatted correctly') + else: + print('Incorrectly formatted files:') + for file in failed_files: + print(f'\"{file}\"') + + return result + +if __name__ == '__main__': + exit(main()) diff --git a/src/libmtrsim/AssignmentRule.cpp b/src/libmtrsim/AssignmentRule.cpp index 90a6ac9..a8cbb81 100644 --- a/src/libmtrsim/AssignmentRule.cpp +++ b/src/libmtrsim/AssignmentRule.cpp @@ -24,12 +24,7 @@ constexpr int k_MaxBisectIter = 2000; // Bisect q in (qMin, qMax) so that qsimvn(identity, sTmp, tTmp with tTmp[goi]=q) == poi. // tTmp is passed by value — the caller receives the final tTmp back via the reference returned. -double bisectThreshold(QSimVN& qsimvn, - const Eigen::MatrixXd& identity, - const Eigen::VectorXd& sTmp, - Eigen::VectorXd& tTmp, - int goi, - double poi) +double bisectThreshold(QSimVN& qsimvn, const Eigen::MatrixXd& identity, const Eigen::VectorXd& sTmp, Eigen::VectorXd& tTmp, int goi, double poi) { double q = 0.0; double qMin = k_QMin0; diff --git a/src/libmtrsim/AssignmentRule.hpp b/src/libmtrsim/AssignmentRule.hpp index 197279b..8c7fe14 100644 --- a/src/libmtrsim/AssignmentRule.hpp +++ b/src/libmtrsim/AssignmentRule.hpp @@ -1,5 +1,6 @@ #pragma once +#include "mtrsim_export.h" #include #include #include @@ -10,7 +11,7 @@ namespace mtrsim /** * @brief Result of threshold selection for the plurigaussian assignment rule. */ -struct AssignmentRuleThresholds +struct MTRSIM_EXPORT AssignmentRuleThresholds { int numGaussians = 0; @@ -26,7 +27,7 @@ struct AssignmentRuleThresholds * * Combines the logic of select_AR.m and eval_AR.m. */ -class AssignmentRule +class MTRSIM_EXPORT AssignmentRule { public: explicit AssignmentRule(std::mt19937_64& rng); @@ -47,8 +48,7 @@ class AssignmentRule * @param thresholds Thresholds returned by selectThresholds() * @return Integer component index (1-based) for each voxel, length N */ - Eigen::VectorXi evaluate(const Eigen::MatrixXd& z, - const AssignmentRuleThresholds& thresholds) const; + Eigen::VectorXi evaluate(const Eigen::MatrixXd& z, const AssignmentRuleThresholds& thresholds) const; private: std::mt19937_64& m_Rng; diff --git a/src/libmtrsim/CMakeLists.txt b/src/libmtrsim/CMakeLists.txt index f8a7002..6c42746 100644 --- a/src/libmtrsim/CMakeLists.txt +++ b/src/libmtrsim/CMakeLists.txt @@ -29,9 +29,13 @@ add_library(mtrsim SHARED ${MTRSIM_HEADERS} ${MTRSIM_SOURCES}) add_library(mtrsim::mtrsim ALIAS mtrsim) +include(GenerateExportHeader) +generate_export_header(mtrsim) + target_include_directories(mtrsim PUBLIC $ + $ $ PRIVATE ${Stb_INCLUDE_DIR} diff --git a/src/libmtrsim/CrystalSymmetry.cpp b/src/libmtrsim/CrystalSymmetry.cpp index 13abab9..16122d8 100644 --- a/src/libmtrsim/CrystalSymmetry.cpp +++ b/src/libmtrsim/CrystalSymmetry.cpp @@ -24,12 +24,12 @@ int CrystalSymmetry::numOperators() const { switch(m_System) { - case CrystalSystem::HCP: - return 12; - case CrystalSystem::FCC: - return 24; - default: - return 0; + case CrystalSystem::HCP: + return 12; + case CrystalSystem::FCC: + return 24; + default: + return 0; } } @@ -55,11 +55,9 @@ int CrystalSymmetry::numOperators() const // phi1 = atan2(R[2,0], -R[2,1]) // phi2 = atan2(R[0,2], R[1,2]) -Eigen::MatrixXd CrystalSymmetry::expand(const Eigen::VectorXd& phi1, - const Eigen::VectorXd& phi, - const Eigen::VectorXd& phi2) const +Eigen::MatrixXd CrystalSymmetry::expand(const Eigen::VectorXd& phi1, const Eigen::VectorXd& phi, const Eigen::VectorXd& phi2) const { - const int N = static_cast(phi1.size()); + const int N = static_cast(phi1.size()); const int numOps = numOperators(); // Instantiate the appropriate LaueOps object @@ -75,25 +73,22 @@ Eigen::MatrixXd CrystalSymmetry::expand(const Eigen::VectorXd& phi1, Eigen::MatrixXd result(N * numOps, 3); - constexpr double k_Eps = 1.0e-6; - const double k_Pi = std::numbers::pi; - const double k_TwoPi = 2.0 * k_Pi; + constexpr double k_Eps = 1.0e-6; + const double k_Pi = std::numbers::pi; + const double k_TwoPi = 2.0 * k_Pi; for(int i = 0; i < N; ++i) { // ── Build passive rotation matrix G from Bunge Euler angles ───────────── const double c1 = std::cos(phi1[i]); const double s1 = std::sin(phi1[i]); - const double C = std::cos(phi[i]); - const double S = std::sin(phi[i]); + const double C = std::cos(phi[i]); + const double S = std::sin(phi[i]); const double c2 = std::cos(phi2[i]); const double s2 = std::sin(phi2[i]); // Row-major layout: G[row*3 + col] - const ebsdlib::Matrix3X3D G( - c1 * c2 - s1 * s2 * C, s1 * c2 + c1 * s2 * C, s2 * S, - -c1 * s2 - s1 * c2 * C, -s1 * s2 + c1 * c2 * C, c2 * S, - s1 * S, -c1 * S, C); + const ebsdlib::Matrix3X3D G(c1 * c2 - s1 * s2 * C, s1 * c2 + c1 * s2 * C, s2 * S, -c1 * s2 - s1 * c2 * C, -s1 * s2 + c1 * c2 * C, c2 * S, s1 * S, -c1 * S, C); for(int k = 0; k < numOps; ++k) { @@ -101,22 +96,22 @@ Eigen::MatrixXd CrystalSymmetry::expand(const Eigen::VectorXd& phi1, // O_passive = O_t.transpose() // R = O_passive * G = O_t.transpose() * G const ebsdlib::Matrix3X3D O_t = ops->getMatSymOpD(static_cast(k)); - const ebsdlib::Matrix3X3D R = O_t.transpose() * G; + const ebsdlib::Matrix3X3D R = O_t.transpose() * G; // ── Extract Euler angles from R (EbsdLib's om2eu convention) ─────────── // Indices in row-major storage: R[8]=R(2,2), R[6]=R(2,0), R[7]=R(2,1), // R[2]=R(0,2), R[5]=R(1,2) const double r33 = R[8]; - double p1 = 0.0; - double ph = 0.0; - double p2 = 0.0; + double p1 = 0.0; + double ph = 0.0; + double p2 = 0.0; if(std::abs(std::abs(r33) - 1.0) > k_Eps) { const double zeta = 1.0 / std::sqrt(1.0 - r33 * r33); ph = std::acos(std::clamp(r33, -1.0, 1.0)); p1 = std::atan2(R[6] * zeta, -R[7] * zeta); - p2 = std::atan2(R[2] * zeta, R[5] * zeta); + p2 = std::atan2(R[2] * zeta, R[5] * zeta); } else if(r33 > 0.0) { @@ -147,7 +142,7 @@ Eigen::MatrixXd CrystalSymmetry::expand(const Eigen::VectorXd& phi1, p2 = std::fmod(p2 + 100.0 * k_TwoPi, k_TwoPi); } - const int row = i * numOps + k; + const int row = i * numOps + k; result(row, 0) = p1; result(row, 1) = ph; result(row, 2) = p2; diff --git a/src/libmtrsim/CrystalSymmetry.hpp b/src/libmtrsim/CrystalSymmetry.hpp index 411ea3a..d4b8984 100644 --- a/src/libmtrsim/CrystalSymmetry.hpp +++ b/src/libmtrsim/CrystalSymmetry.hpp @@ -1,5 +1,6 @@ #pragma once +#include "mtrsim_export.h" #include namespace mtrsim @@ -23,7 +24,7 @@ enum class CrystalSystem * tables, using HexagonalOps::getMatSymOpD(i) and * OrientationTransformation::eu2om / om2eu for the rotation math. */ -class CrystalSymmetry +class MTRSIM_EXPORT CrystalSymmetry { public: explicit CrystalSymmetry(CrystalSystem system = CrystalSystem::HCP); @@ -36,9 +37,7 @@ class CrystalSymmetry * @param phi2 Input phi2 angles [rad], length N * @return Matrix of shape [N*numOps x 3] with columns [phi1, PHI, phi2] */ - Eigen::MatrixXd expand(const Eigen::VectorXd& phi1, - const Eigen::VectorXd& phi, - const Eigen::VectorXd& phi2) const; + Eigen::MatrixXd expand(const Eigen::VectorXd& phi1, const Eigen::VectorXd& phi, const Eigen::VectorXd& phi2) const; /// Returns the number of symmetry operators for the configured crystal system. int numOperators() const; diff --git a/src/libmtrsim/GPGenerator.cpp b/src/libmtrsim/GPGenerator.cpp index de13c11..45078b1 100644 --- a/src/libmtrsim/GPGenerator.cpp +++ b/src/libmtrsim/GPGenerator.cpp @@ -65,9 +65,7 @@ Eigen::MatrixXd GPGenerator::buildCovarianceMatrix(int n, double spacing, double // MATLAB: W_1 * Rz → C++: W1 * llt_z.matrixU() // MATLAB: Ry' * W2k * Rx → C++: llt_y.matrixL() * W2k * llt_x.matrixU() -Eigen::VectorXd GPGenerator::generate(double hx, double hy, double hz, - const std::array& theta, - int nx, int ny, int nz) +Eigen::VectorXd GPGenerator::generate(double hx, double hy, double hz, const std::array& theta, int nx, int ny, int nz) { // ── Build per-direction covariance matrices ───────────────────────────── const Eigen::MatrixXd Gamma_x = buildCovarianceMatrix(nx, hx, theta[0]); @@ -86,9 +84,9 @@ Eigen::VectorXd GPGenerator::generate(double hx, double hy, double hz, // MATLAB upper-triangular Cholesky factors: Rx = llt_x.matrixU() (= L_x') // Used below as: W1 * Rz and Ry' * W2k * Rx - const Eigen::MatrixXd Rx = llt_x.matrixU(); // nx × nx - const Eigen::MatrixXd Ry_lower = llt_y.matrixL(); // ny × ny (= MATLAB Ry') - const Eigen::MatrixXd Rz = llt_z.matrixU(); // nz × nz + const Eigen::MatrixXd Rx = llt_x.matrixU(); // nx × nx + const Eigen::MatrixXd Ry_lower = llt_y.matrixL(); // ny × ny (= MATLAB Ry') + const Eigen::MatrixXd Rz = llt_z.matrixU(); // nz × nz // ── Standard normal draw ───────────────────────────────────────────────── const int N = nx * ny * nz; diff --git a/src/libmtrsim/GPGenerator.hpp b/src/libmtrsim/GPGenerator.hpp index 0a3f65a..330eafd 100644 --- a/src/libmtrsim/GPGenerator.hpp +++ b/src/libmtrsim/GPGenerator.hpp @@ -1,5 +1,6 @@ #pragma once +#include "mtrsim_export.h" #include #include #include @@ -21,7 +22,7 @@ namespace mtrsim * where Rx, Ry, Rz are the Cholesky factors of the per-direction covariance * matrices. */ -class GPGenerator +class MTRSIM_EXPORT GPGenerator { public: using CorrelationFn = std::function; @@ -45,9 +46,7 @@ class GPGenerator * @param nz Number of voxels in z * @return Flattened field of length nx*ny*nz (z-major ordering) */ - Eigen::VectorXd generate(double hx, double hy, double hz, - const std::array& theta, - int nx, int ny, int nz); + Eigen::VectorXd generate(double hx, double hy, double hz, const std::array& theta, int nx, int ny, int nz); private: Eigen::MatrixXd buildCovarianceMatrix(int n, double spacing, double theta) const; diff --git a/src/libmtrsim/IPFMapper.cpp b/src/libmtrsim/IPFMapper.cpp index 232d1a1..ecb1739 100644 --- a/src/libmtrsim/IPFMapper.cpp +++ b/src/libmtrsim/IPFMapper.cpp @@ -43,10 +43,7 @@ IPFMapper::IPFMapper(CrystalSystem system) // Therefore O_passive = getMatSymOpD(k).transpose(), and h_rot = getMatSymOpD(k).transpose() * h_G. // With row-major flat indexing: h_rot[r] = Ot[0+r]*hx + Ot[3+r]*hy + Ot[6+r]*hz. -std::vector IPFMapper::eulerToColors(const Eigen::VectorXd& phi1, - const Eigen::VectorXd& phi, - const Eigen::VectorXd& phi2, - std::array refDir) const +std::vector IPFMapper::eulerToColors(const Eigen::VectorXd& phi1, const Eigen::VectorXd& phi, const Eigen::VectorXd& phi2, std::array refDir) const { const int N = static_cast(phi1.size()); if(phi.size() != N || phi2.size() != N) @@ -59,12 +56,12 @@ std::vector IPFMapper::eulerToColors(const Eigen::VectorXd& phi1, int numOps = 0; if(m_System == CrystalSystem::HCP) { - ops = ebsdlib::HexagonalOps::New(); + ops = ebsdlib::HexagonalOps::New(); numOps = 12; } else if(m_System == CrystalSystem::FCC) { - ops = ebsdlib::CubicOps::New(); + ops = ebsdlib::CubicOps::New(); numOps = 24; } else @@ -85,8 +82,8 @@ std::vector IPFMapper::eulerToColors(const Eigen::VectorXd& phi1, { const double c1 = std::cos(phi1[i]); const double s1 = std::sin(phi1[i]); - const double C = std::cos(phi[i]); - const double S = std::sin(phi[i]); + const double C = std::cos(phi[i]); + const double S = std::sin(phi[i]); const double c2 = std::cos(phi2[i]); const double s2 = std::sin(phi2[i]); @@ -100,7 +97,7 @@ std::vector IPFMapper::eulerToColors(const Eigen::VectorXd& phi1, double X_best = 0.0; double Y_best = 0.0; - bool found = false; + bool found = false; for(int k = 0; k < numOps; ++k) { @@ -108,9 +105,9 @@ std::vector IPFMapper::eulerToColors(const Eigen::VectorXd& phi1, // Row-major flat: Ot[r*3+c] = Ot(r,c); Ot.T(r,c) = Ot[c*3+r] // h_rot[r] = Ot[0+r]*hx_G + Ot[3+r]*hy_G + Ot[6+r]*hz_G const ebsdlib::Matrix3X3D Ot = ops->getMatSymOpD(static_cast(k)); - const double hx = Ot[0] * hx_G + Ot[3] * hy_G + Ot[6] * hz_G; - const double hy = Ot[1] * hx_G + Ot[4] * hy_G + Ot[7] * hz_G; - const double hz = Ot[2] * hx_G + Ot[5] * hy_G + Ot[8] * hz_G; + const double hx = Ot[0] * hx_G + Ot[3] * hy_G + Ot[6] * hz_G; + const double hy = Ot[1] * hx_G + Ot[4] * hy_G + Ot[7] * hz_G; + const double hz = Ot[2] * hx_G + Ot[5] * hy_G + Ot[8] * hz_G; // ── Flip to lower hemisphere (MATLAB: if h_rot'*[0 0 -1]' < 0, flip) ── const double hx_f = (hz > 0.0) ? -hx : hx; @@ -131,7 +128,7 @@ std::vector IPFMapper::eulerToColors(const Eigen::VectorXd& phi1, { X_best = X; Y_best = Y; - found = true; + found = true; break; } } @@ -140,13 +137,13 @@ std::vector IPFMapper::eulerToColors(const Eigen::VectorXd& phi1, { // Fallback: use k=0 (identity) projection regardless of fundamental zone. const ebsdlib::Matrix3X3D Ot0 = ops->getMatSymOpD(0); - const double hx0 = Ot0[0] * hx_G + Ot0[3] * hy_G + Ot0[6] * hz_G; - const double hy0 = Ot0[1] * hx_G + Ot0[4] * hy_G + Ot0[7] * hz_G; - const double hz0 = Ot0[2] * hx_G + Ot0[5] * hy_G + Ot0[8] * hz_G; - const double hx_f0 = (hz0 > 0.0) ? -hx0 : hx0; - const double hy_f0 = (hz0 > 0.0) ? -hy0 : hy0; - const double hz_f0 = (hz0 > 0.0) ? -hz0 : hz0; - const double denom0 = 1.0 - hz_f0; + const double hx0 = Ot0[0] * hx_G + Ot0[3] * hy_G + Ot0[6] * hz_G; + const double hy0 = Ot0[1] * hx_G + Ot0[4] * hy_G + Ot0[7] * hz_G; + const double hz0 = Ot0[2] * hx_G + Ot0[5] * hy_G + Ot0[8] * hz_G; + const double hx_f0 = (hz0 > 0.0) ? -hx0 : hx0; + const double hy_f0 = (hz0 > 0.0) ? -hy0 : hy0; + const double hz_f0 = (hz0 > 0.0) ? -hz0 : hz0; + const double denom0 = 1.0 - hz_f0; if(std::abs(denom0) > 1.0e-10) { X_best = hx_f0 / denom0; @@ -158,12 +155,12 @@ std::vector IPFMapper::eulerToColors(const Eigen::VectorXd& phi1, // r = sqrt(X²+Y²), beta = atan2(Y, X) // cmap1 = 1−r, cmap2 = r*(1−beta/(π/6)), cmap3 = r*(beta/(π/6)) // Normalise each channel by max_c = max(cmap1, cmap2, cmap3). - const double r = std::sqrt(X_best * X_best + Y_best * Y_best); + const double r = std::sqrt(X_best * X_best + Y_best * Y_best); const double beta = std::atan2(Y_best, X_best); const double inv6overPi = 6.0 / std::numbers::pi; - double c_r = 1.0 - r; - double c_g = r * (1.0 - beta * inv6overPi); - double c_b = r * (beta * inv6overPi); + double c_r = 1.0 - r; + double c_g = r * (1.0 - beta * inv6overPi); + double c_b = r * (beta * inv6overPi); const double max_c = std::max({c_r, c_g, c_b}); if(max_c > 1.0e-10) @@ -173,10 +170,10 @@ std::vector IPFMapper::eulerToColors(const Eigen::VectorXd& phi1, c_b /= max_c; } - const std::size_t idx = static_cast(i); - colors[idx].r = static_cast(std::clamp(c_r, 0.0, 1.0) * 255.0 + 0.5); - colors[idx].g = static_cast(std::clamp(c_g, 0.0, 1.0) * 255.0 + 0.5); - colors[idx].b = static_cast(std::clamp(c_b, 0.0, 1.0) * 255.0 + 0.5); + const std::size_t idx = static_cast(i); + colors[idx].r = static_cast(std::clamp(c_r, 0.0, 1.0) * 255.0 + 0.5); + colors[idx].g = static_cast(std::clamp(c_g, 0.0, 1.0) * 255.0 + 0.5); + colors[idx].b = static_cast(std::clamp(c_b, 0.0, 1.0) * 255.0 + 0.5); } return colors; @@ -189,11 +186,7 @@ std::vector IPFMapper::eulerToColors(const Eigen::VectorXd& phi1, // x/y values (with tolerance 1e-6 × max range). Each point maps to a pixel // at (col, row) = (x_rank, y_rank). Y increases downward (row 0 = min y). -void IPFMapper::writePNG(const Eigen::MatrixXd& spatialCoords, - const Eigen::VectorXd& phi1_in, - const Eigen::VectorXd& phi_in, - const Eigen::VectorXd& phi2_in, - const std::string& outputPath) const +void IPFMapper::writePNG(const Eigen::MatrixXd& spatialCoords, const Eigen::VectorXd& phi1_in, const Eigen::VectorXd& phi_in, const Eigen::VectorXd& phi2_in, const std::string& outputPath) const { const int N = static_cast(phi1_in.size()); if(spatialCoords.rows() != N || spatialCoords.cols() < 2) @@ -209,10 +202,9 @@ void IPFMapper::writePNG(const Eigen::MatrixXd& spatialCoords, const double xRange = xCol.maxCoeff() - xCol.minCoeff(); const double yRange = yCol.maxCoeff() - yCol.minCoeff(); - const double tol = 1.0e-6 * std::max({xRange, yRange, 1.0}); + const double tol = 1.0e-6 * std::max({xRange, yRange, 1.0}); - auto sortedUnique = [&](const Eigen::VectorXd& v) -> std::vector - { + auto sortedUnique = [&](const Eigen::VectorXd& v) -> std::vector { std::vector vals(v.data(), v.data() + v.size()); std::sort(vals.begin(), vals.end()); std::vector unique; @@ -228,8 +220,8 @@ void IPFMapper::writePNG(const Eigen::MatrixXd& spatialCoords, const std::vector xUnique = sortedUnique(xCol); const std::vector yUnique = sortedUnique(yCol); - const int width = static_cast(xUnique.size()); - const int height = static_cast(yUnique.size()); + const int width = static_cast(xUnique.size()); + const int height = static_cast(yUnique.size()); if(width < 1 || height < 1) { @@ -239,8 +231,7 @@ void IPFMapper::writePNG(const Eigen::MatrixXd& spatialCoords, // ── Fill pixel buffer (RGB, 3 bytes per pixel) ────────────────────────────── std::vector pixels(static_cast(width * height * 3), 0u); - auto findRank = [&](const std::vector& sorted, double val) -> int - { + auto findRank = [&](const std::vector& sorted, double val) -> int { const auto it = std::lower_bound(sorted.begin(), sorted.end(), val - tol); return static_cast(std::distance(sorted.begin(), it)); }; @@ -255,9 +246,9 @@ void IPFMapper::writePNG(const Eigen::MatrixXd& spatialCoords, } const std::size_t px = static_cast((row * width + col) * 3); const std::size_t ci = static_cast(i); - pixels[px] = colors[ci].r; - pixels[px + 1] = colors[ci].g; - pixels[px + 2] = colors[ci].b; + pixels[px] = colors[ci].r; + pixels[px + 1] = colors[ci].g; + pixels[px + 2] = colors[ci].b; } // ── Write PNG ─────────────────────────────────────────────────────────────── diff --git a/src/libmtrsim/IPFMapper.hpp b/src/libmtrsim/IPFMapper.hpp index 355ea94..63a9169 100644 --- a/src/libmtrsim/IPFMapper.hpp +++ b/src/libmtrsim/IPFMapper.hpp @@ -1,6 +1,7 @@ #pragma once #include "CrystalSymmetry.hpp" +#include "mtrsim_export.h" #include #include #include @@ -12,7 +13,7 @@ namespace mtrsim /** * @brief RGB colour triple in [0, 255] uint8 range. */ -struct RGBColor +struct MTRSIM_EXPORT RGBColor { uint8_t r = 0; uint8_t g = 0; @@ -26,7 +27,7 @@ struct RGBColor * Delegates colour computation to EbsdLib's HexagonalOps::generateIPFColor(), * which performs the stereographic projection and colour mapping in one step. */ -class IPFMapper +class MTRSIM_EXPORT IPFMapper { public: explicit IPFMapper(CrystalSystem system = CrystalSystem::HCP); @@ -44,10 +45,7 @@ class IPFMapper * @param refDir Reference direction in sample frame (default: Z = {0,0,1}) * @return Per-voxel RGB colours, length N, values in [0, 255] */ - std::vector eulerToColors(const Eigen::VectorXd& phi1, - const Eigen::VectorXd& phi, - const Eigen::VectorXd& phi2, - std::array refDir = {0.0, 0.0, 1.0}) const; + std::vector eulerToColors(const Eigen::VectorXd& phi1, const Eigen::VectorXd& phi, const Eigen::VectorXd& phi2, std::array refDir = {0.0, 0.0, 1.0}) const; /** * @brief Render an IPF map image and write it to a PNG file. @@ -58,11 +56,7 @@ class IPFMapper * @param phi2 phi2 angles [rad] * @param outputPath Destination PNG file path */ - void writePNG(const Eigen::MatrixXd& spatialCoords, - const Eigen::VectorXd& phi1, - const Eigen::VectorXd& phi, - const Eigen::VectorXd& phi2, - const std::string& outputPath) const; + void writePNG(const Eigen::MatrixXd& spatialCoords, const Eigen::VectorXd& phi1, const Eigen::VectorXd& phi, const Eigen::VectorXd& phi2, const std::string& outputPath) const; private: CrystalSystem m_System; diff --git a/src/libmtrsim/MTRDataLoader.cpp b/src/libmtrsim/MTRDataLoader.cpp index 2c1d58d..5f6853c 100644 --- a/src/libmtrsim/MTRDataLoader.cpp +++ b/src/libmtrsim/MTRDataLoader.cpp @@ -171,9 +171,9 @@ EBSDData MTRDataLoader::load(const std::string& directoryPath) data.isMTR.resize(N); for(int i = 0; i < N; ++i) { - const std::size_t si = static_cast(i); - data.parentIds[i] = static_cast(parentVals[si]); - data.isMTR[i] = (boolVals[si] != 0.0); + const std::size_t si = static_cast(i); + data.parentIds[i] = static_cast(parentVals[si]); + data.isMTR[i] = (boolVals[si] != 0.0); } return data; diff --git a/src/libmtrsim/MTRDataLoader.hpp b/src/libmtrsim/MTRDataLoader.hpp index c3db272..36275d1 100644 --- a/src/libmtrsim/MTRDataLoader.hpp +++ b/src/libmtrsim/MTRDataLoader.hpp @@ -1,5 +1,6 @@ #pragma once +#include "mtrsim_export.h" #include #include @@ -11,7 +12,7 @@ namespace mtrsim * * Matches the variables read by load_MTR_data.m. */ -struct EBSDData +struct MTRSIM_EXPORT EBSDData { Eigen::MatrixXd spatialCoords; ///< [N x 2] (X, Y) positions [mm] Eigen::MatrixXd eulerAngles; ///< [N x 3] (phi1, PHI, phi2) [rad] @@ -29,7 +30,7 @@ struct EBSDData * - ParentIds.csv (N rows × 1 col, integer grain IDs) * - BoolMTR.csv (N rows × 1 col, 0 or 1) */ -class MTRDataLoader +class MTRSIM_EXPORT MTRDataLoader { public: MTRDataLoader() = default; diff --git a/src/libmtrsim/ODFCalculator.cpp b/src/libmtrsim/ODFCalculator.cpp index ad3678a..f25ea67 100644 --- a/src/libmtrsim/ODFCalculator.cpp +++ b/src/libmtrsim/ODFCalculator.cpp @@ -29,10 +29,7 @@ namespace mtrsim // Wrapping: periodic in phi1 and phi2; PHI also wraps for the smoothing // neighbours (matching MATLAB behaviour). -ODFComponent ODFCalculator::compute(const Eigen::VectorXd& phi1in, - const Eigen::VectorXd& phiIn, - const Eigen::VectorXd& phi2in, - double degSpacing) +ODFComponent ODFCalculator::compute(const Eigen::VectorXd& phi1in, const Eigen::VectorXd& phiIn, const Eigen::VectorXd& phi2in, double degSpacing) { if(phi1in.size() != phiIn.size() || phi1in.size() != phi2in.size()) { @@ -40,14 +37,14 @@ ODFComponent ODFCalculator::compute(const Eigen::VectorXd& phi1in, } // ── Grid constants ──────────────────────────────────────────────────────── - const int nBins1 = static_cast(std::round(360.0 / degSpacing)); // 72 - const int nBinsPHI = nBins1 / 2; // 36 - const int nBins2 = nBins1; // 72 - const int nTotal = nBins1 * nBinsPHI * nBins2; // 186 624 + const int nBins1 = static_cast(std::round(360.0 / degSpacing)); // 72 + const int nBinsPHI = nBins1 / 2; // 36 + const int nBins2 = nBins1; // 72 + const int nTotal = nBins1 * nBinsPHI * nBins2; // 186 624 - const double radSpacing = degSpacing * std::numbers::pi / 180.0; + const double radSpacing = degSpacing * std::numbers::pi / 180.0; const double k_TwoPiOver = 2.0 * std::numbers::pi / static_cast(nBins1); - const double k_PiOver = std::numbers::pi / static_cast(nBinsPHI); + const double k_PiOver = std::numbers::pi / static_cast(nBinsPHI); // ── Pre-compute bin centres for the output ODFComponent ────────────────── Eigen::VectorXd phi1Bins(nTotal); @@ -56,37 +53,34 @@ ODFComponent ODFCalculator::compute(const Eigen::VectorXd& phi1in, for(int ix = 0; ix < nTotal; ++ix) { - const int i1 = ix / (nBinsPHI * nBins2); + const int i1 = ix / (nBinsPHI * nBins2); const int iPHI = (ix % (nBinsPHI * nBins2)) / nBins2; - const int i2 = ix % nBins2; + const int i2 = ix % nBins2; phi1Bins[ix] = (i1 + 0.5) * k_TwoPiOver; - phiBins[ix] = (iPHI + 0.5) * k_PiOver; + phiBins[ix] = (iPHI + 0.5) * k_PiOver; phi2Bins[ix] = (i2 + 0.5) * k_TwoPiOver; } // ── Obtain all symmetric Euler angles (HCP, matching MATLAB default) ────── CrystalSymmetry cs{CrystalSystem::HCP}; const Eigen::MatrixXd symAngles = cs.expand(phi1in, phiIn, phi2in); - const int N = static_cast(symAngles.rows()); // N_input × 12 + const int N = static_cast(symAngles.rows()); // N_input × 12 // ── Smoothing factors ───────────────────────────────────────────────────── - const double normFactor = 1.0 / static_cast(N); + const double normFactor = 1.0 / static_cast(N); const double centerFactor = 0.332; - const double faceFactor = 0.448 / 6.0; - const double edgeFactor = 0.16 / 12.0; + const double faceFactor = 0.448 / 6.0; + const double edgeFactor = 0.16 / 12.0; const double cornerFactor = 0.06 / 8.0; // ── Helper lambdas for periodic neighbour wrapping ──────────────────────── // phi1 and phi2 are periodic over nBins1/nBins2 bins. // PHI also wraps for the smoothing kernel (matching MATLAB). - auto wrap1 = [nBins1](int idx) -> int { return (idx % nBins1 + nBins1) % nBins1; }; + auto wrap1 = [nBins1](int idx) -> int { return (idx % nBins1 + nBins1) % nBins1; }; auto wrapPHI = [nBinsPHI](int idx) -> int { return (idx % nBinsPHI + nBinsPHI) % nBinsPHI; }; - auto wrap2 = [nBins2](int idx) -> int { return (idx % nBins2 + nBins2) % nBins2; }; + auto wrap2 = [nBins2](int idx) -> int { return (idx % nBins2 + nBins2) % nBins2; }; - auto flatIdx = [nBinsPHI, nBins2](int i1, int iPHI, int i2) -> int - { - return i1 * nBinsPHI * nBins2 + iPHI * nBins2 + i2; - }; + auto flatIdx = [nBinsPHI, nBins2](int i1, int iPHI, int i2) -> int { return i1 * nBinsPHI * nBins2 + iPHI * nBins2 + i2; }; // ── Accumulate ODF ──────────────────────────────────────────────────────── Eigen::VectorXd odfVal = Eigen::VectorXd::Zero(nTotal); @@ -145,9 +139,9 @@ ODFComponent ODFCalculator::compute(const Eigen::VectorXd& phi1in, } ODFComponent result; - result.odfVal = std::move(odfVal); + result.odfVal = std::move(odfVal); result.phi1Bins = std::move(phi1Bins); - result.phiBins = std::move(phiBins); + result.phiBins = std::move(phiBins); result.phi2Bins = std::move(phi2Bins); return result; } diff --git a/src/libmtrsim/ODFCalculator.hpp b/src/libmtrsim/ODFCalculator.hpp index 4346643..e5e02bb 100644 --- a/src/libmtrsim/ODFCalculator.hpp +++ b/src/libmtrsim/ODFCalculator.hpp @@ -1,6 +1,7 @@ #pragma once #include "ODFSampler.hpp" +#include "mtrsim_export.h" #include namespace mtrsim @@ -12,7 +13,7 @@ namespace mtrsim * Equivalent to calc_ODF.m. Applies crystal symmetry operations (HCP by * default) and optional nearest-neighbour smoothing before binning. */ -class ODFCalculator +class MTRSIM_EXPORT ODFCalculator { public: ODFCalculator() = default; @@ -26,10 +27,7 @@ class ODFCalculator * @param degSpacing Bin width in degrees (default 5°) * @return Populated ODFComponent with normalised ODFval */ - ODFComponent compute(const Eigen::VectorXd& phi1, - const Eigen::VectorXd& phi, - const Eigen::VectorXd& phi2, - double degSpacing = 5.0); + ODFComponent compute(const Eigen::VectorXd& phi1, const Eigen::VectorXd& phi, const Eigen::VectorXd& phi2, double degSpacing = 5.0); }; } // namespace mtrsim diff --git a/src/libmtrsim/ODFSampler.cpp b/src/libmtrsim/ODFSampler.cpp index 10adf67..cb67a43 100644 --- a/src/libmtrsim/ODFSampler.cpp +++ b/src/libmtrsim/ODFSampler.cpp @@ -28,18 +28,14 @@ ODFSampler::ODFSampler(std::mt19937_64& rng) // The bin width is inferred as uniform.phi1Bins[1] − uniform.phi1Bins[0], // matching MATLAB's bin_scaling_factor = degree_spacing/180*pi. -Eigen::MatrixXd ODFSampler::sampleN(int n, - const ODFComponent& component, - const ODFComponent& uniform) +Eigen::MatrixXd ODFSampler::sampleN(int n, const ODFComponent& component, const ODFComponent& uniform) { const int nBins = static_cast(component.odfVal.size()); if(nBins < 1) { throw std::invalid_argument("ODFSampler::sampleN — odfVal is empty"); } - if(uniform.phi1Bins.size() != static_cast(nBins) || - uniform.phiBins.size() != static_cast(nBins) || - uniform.phi2Bins.size() != static_cast(nBins)) + if(uniform.phi1Bins.size() != static_cast(nBins) || uniform.phiBins.size() != static_cast(nBins) || uniform.phi2Bins.size() != static_cast(nBins)) { throw std::invalid_argument("ODFSampler::sampleN — uniform bin vectors do not match odfVal size"); } @@ -59,11 +55,11 @@ Eigen::MatrixXd ODFSampler::sampleN(int n, for(int i = 0; i < n; ++i) { - const double u = uDist(m_Rng); + const double u = uDist(m_Rng); // upper_bound finds the first position where cdf > u; // the bin index is one before that position. - const auto it = std::upper_bound(cdf.cbegin(), cdf.cend(), u); - int bin = static_cast(std::distance(cdf.cbegin(), it)) - 1; + const auto it = std::upper_bound(cdf.cbegin(), cdf.cend(), u); + int bin = static_cast(std::distance(cdf.cbegin(), it)) - 1; binIdx[static_cast(i)] = std::clamp(bin, 0, nBins - 1); } @@ -72,8 +68,7 @@ Eigen::MatrixXd ODFSampler::sampleN(int n, // ── Infer bin width from the uniform ODF bin centres ───────────────────── // For standard 5° spacing: binWidth ≈ 5*π/180 rad. - const double binWidth = (nBins > 1) ? (uniform.phi1Bins[1] - uniform.phi1Bins[0]) - : (5.0 * std::acos(-1.0) / 180.0); + const double binWidth = (nBins > 1) ? (uniform.phi1Bins[1] - uniform.phi1Bins[0]) : (5.0 * std::acos(-1.0) / 180.0); // ── Assign bin-centre coordinates with independent per-angle jitter ─────── std::uniform_real_distribution jitter(-0.5 * binWidth, 0.5 * binWidth); @@ -81,10 +76,10 @@ Eigen::MatrixXd ODFSampler::sampleN(int n, Eigen::MatrixXd result(n, 3); for(int i = 0; i < n; ++i) { - const int b = binIdx[static_cast(i)]; - result(i, 0) = uniform.phi1Bins[b] + jitter(m_Rng); - result(i, 1) = uniform.phiBins[b] + jitter(m_Rng); - result(i, 2) = uniform.phi2Bins[b] + jitter(m_Rng); + const int b = binIdx[static_cast(i)]; + result(i, 0) = uniform.phi1Bins[b] + jitter(m_Rng); + result(i, 1) = uniform.phiBins[b] + jitter(m_Rng); + result(i, 2) = uniform.phi2Bins[b] + jitter(m_Rng); } return result; @@ -95,8 +90,7 @@ Eigen::MatrixXd ODFSampler::sampleN(int n, // // Uses the same inverse-CDF approach as sampleN but draws a single orientation. -EulerAngles ODFSampler::sampleOne(const ODFComponent& component, - const ODFComponent& uniform) +EulerAngles ODFSampler::sampleOne(const ODFComponent& component, const ODFComponent& uniform) { const Eigen::MatrixXd row = sampleN(1, component, uniform); return EulerAngles{row(0, 0), row(0, 1), row(0, 2)}; diff --git a/src/libmtrsim/ODFSampler.hpp b/src/libmtrsim/ODFSampler.hpp index 338fcd8..4252bb2 100644 --- a/src/libmtrsim/ODFSampler.hpp +++ b/src/libmtrsim/ODFSampler.hpp @@ -1,5 +1,6 @@ #pragma once +#include "mtrsim_export.h" #include #include #include @@ -12,21 +13,21 @@ namespace mtrsim * * Matches the MATLAB struct fields stored in simulation_ODF.h5. */ -struct ODFComponent +struct MTRSIM_EXPORT ODFComponent { - Eigen::VectorXd odfVal; ///< Probability mass for each Euler-space bin (N_bins,) - Eigen::VectorXd phi1Bins; ///< phi1 bin centres [rad] - Eigen::VectorXd phiBins; ///< PHI bin centres [rad] - Eigen::VectorXd phi2Bins; ///< phi2 bin centres [rad] + Eigen::VectorXd odfVal; ///< Probability mass for each Euler-space bin (N_bins,) + Eigen::VectorXd phi1Bins; ///< phi1 bin centres [rad] + Eigen::VectorXd phiBins; ///< PHI bin centres [rad] + Eigen::VectorXd phi2Bins; ///< phi2 bin centres [rad] }; /** * @brief Sampled orientation (Bunge Euler angles, radians). */ -struct EulerAngles +struct MTRSIM_EXPORT EulerAngles { double phi1 = 0.0; - double phi = 0.0; + double phi = 0.0; double phi2 = 0.0; }; @@ -36,7 +37,7 @@ struct EulerAngles * Combines sample_orientation_from_ODF.m and * sample_N_orientations_from_ODF.m. */ -class ODFSampler +class MTRSIM_EXPORT ODFSampler { public: explicit ODFSampler(std::mt19937_64& rng); @@ -49,15 +50,12 @@ class ODFSampler * @param uniform Uniform (reference) ODF component for bin coordinates * @return Matrix of shape [N x 3]: columns are phi1, PHI, phi2 [rad] */ - Eigen::MatrixXd sampleN(int n, - const ODFComponent& component, - const ODFComponent& uniform); + Eigen::MatrixXd sampleN(int n, const ODFComponent& component, const ODFComponent& uniform); /** * @brief Draw a single orientation from the given component ODF. */ - EulerAngles sampleOne(const ODFComponent& component, - const ODFComponent& uniform); + EulerAngles sampleOne(const ODFComponent& component, const ODFComponent& uniform); private: std::mt19937_64& m_Rng; diff --git a/src/libmtrsim/PGRFSimulation.cpp b/src/libmtrsim/PGRFSimulation.cpp index be6b517..3394186 100644 --- a/src/libmtrsim/PGRFSimulation.cpp +++ b/src/libmtrsim/PGRFSimulation.cpp @@ -54,8 +54,7 @@ PGRFResult PGRFSimulation::run(const SimulationParams& params) throw std::invalid_argument("PGRFSimulation: thetaList must have one row per latent Gaussian"); } - spdlog::info("PGRFSimulation: grid {}x{}x{} = {} voxels, {} components, {} latent fields", - nx, ny, nz, N, numComponents, numGaussians); + spdlog::info("PGRFSimulation: grid {}x{}x{} = {} voxels, {} components, {} latent fields", nx, ny, nz, N, numComponents, numGaussians); // ── Select assignment-rule thresholds ──────────────────────────────────── spdlog::info("PGRFSimulation: selecting assignment rule thresholds..."); @@ -64,10 +63,7 @@ PGRFResult PGRFSimulation::run(const SimulationParams& params) // ── Exponential correlation function: rho(lag, theta) = exp(-|lag|/theta) ─ // Matches MATLAB: corr_func_name = 'exp', boundary_conditions = 'nonperiodic' - auto corrFn = [](double lag, double theta) -> double - { - return std::exp(-std::abs(lag) / theta); - }; + auto corrFn = [](double lag, double theta) -> double { return std::exp(-std::abs(lag) / theta); }; GPGenerator gpGen(m_Rng, corrFn); @@ -97,9 +93,7 @@ PGRFResult PGRFSimulation::run(const SimulationParams& params) for(int j = 1; j <= numComponents; ++j) { const int count = (mtrIndex.array() == j).count(); - spdlog::info(" P{} empirical = {:.3f} (target {:.3f})", - j, static_cast(count) / static_cast(N), - params.volumeFractions[static_cast(j - 1)]); + spdlog::info(" P{} empirical = {:.3f} (target {:.3f})", j, static_cast(count) / static_cast(N), params.volumeFractions[static_cast(j - 1)]); } return PGRFResult{mtrIndex, zAll}; diff --git a/src/libmtrsim/PGRFSimulation.hpp b/src/libmtrsim/PGRFSimulation.hpp index 083faf7..8272ff4 100644 --- a/src/libmtrsim/PGRFSimulation.hpp +++ b/src/libmtrsim/PGRFSimulation.hpp @@ -1,6 +1,7 @@ #pragma once #include "SimulationParams.hpp" +#include "mtrsim_export.h" #include #include @@ -10,7 +11,7 @@ namespace mtrsim /** * @brief Result of the plurigaussian random field simulation. */ -struct PGRFResult +struct MTRSIM_EXPORT PGRFResult { // Component assignment for each voxel (1-based index), length N Eigen::VectorXi mtrIndex; @@ -27,7 +28,7 @@ struct PGRFResult * AssignmentRule to produce a categorical assignment over the simulation * volume. */ -class PGRFSimulation +class MTRSIM_EXPORT PGRFSimulation { public: explicit PGRFSimulation(std::mt19937_64& rng); diff --git a/src/libmtrsim/PoleFigure.cpp b/src/libmtrsim/PoleFigure.cpp index 7a680eb..a27e11e 100644 --- a/src/libmtrsim/PoleFigure.cpp +++ b/src/libmtrsim/PoleFigure.cpp @@ -31,9 +31,7 @@ PoleFigureData PoleFigure::fromODF(const ODFComponent& component, double degSpac { throw std::invalid_argument("PoleFigure::fromODF — empty ODFComponent"); } - if(component.phi1Bins.size() != static_cast(nTotal) || - component.phiBins.size() != static_cast(nTotal) || - component.phi2Bins.size() != static_cast(nTotal)) + if(component.phi1Bins.size() != static_cast(nTotal) || component.phiBins.size() != static_cast(nTotal) || component.phi2Bins.size() != static_cast(nTotal)) { throw std::invalid_argument("PoleFigure::fromODF — ODFComponent bin vectors have inconsistent sizes"); } @@ -41,10 +39,10 @@ PoleFigureData PoleFigure::fromODF(const ODFComponent& component, double degSpac // ── Euler-space bin spacings ───────────────────────────────────────────────── // dphi1 = dphi2 = dPHI = degSpacing * π / 180 for the standard layout. const double radSpacing = degSpacing * std::numbers::pi / 180.0; - const double dphi1 = radSpacing; - const double dphi2 = radSpacing; - const double dPHI = radSpacing; - const double halfDPHI = 0.5 * dPHI; + const double dphi1 = radSpacing; + const double dphi2 = radSpacing; + const double dPHI = radSpacing; + const double halfDPHI = 0.5 * dPHI; // Prefactor from MATLAB: 4π² / (dphi1 * dphi2) const double prefactor = 4.0 * std::numbers::pi * std::numbers::pi / (dphi1 * dphi2); @@ -68,15 +66,15 @@ PoleFigureData PoleFigure::fromODF(const ODFComponent& component, double degSpac } const double phi1_m = component.phi1Bins[m]; - const double PHI_m = component.phiBins[m]; + const double PHI_m = component.phiBins[m]; // ── [0001] direction in sample frame: last row of G ────────────────────── // G[2,0] = sin(phi1)*sin(PHI), G[2,1] = −cos(phi1)*sin(PHI), G[2,2] = cos(PHI) const double sinPHI = std::sin(PHI_m); const double cosPHI = std::cos(PHI_m); - const double h_x = std::sin(phi1_m) * sinPHI; - const double h_y = -std::cos(phi1_m) * sinPHI; - const double h_z = cosPHI; + const double h_x = std::sin(phi1_m) * sinPHI; + const double h_y = -std::cos(phi1_m) * sinPHI; + const double h_z = cosPHI; // ── Stereographic projection from south pole ────────────────────────────── // Singularity when h_z → −1 (PHI → π): skip those bins. @@ -115,8 +113,8 @@ PoleFigureData PoleFigure::fromODF(const ODFComponent& component, double degSpac // ── Pack into PoleFigureData ────────────────────────────────────────────────── const int nOut = static_cast(xVec.size()); PoleFigureData pf; - pf.x = Eigen::Map(xVec.data(), nOut); - pf.y = Eigen::Map(yVec.data(), nOut); + pf.x = Eigen::Map(xVec.data(), nOut); + pf.y = Eigen::Map(yVec.data(), nOut); pf.intensity = Eigen::Map(wVec.data(), nOut); return pf; } diff --git a/src/libmtrsim/PoleFigure.hpp b/src/libmtrsim/PoleFigure.hpp index aa02d8e..d9c6f30 100644 --- a/src/libmtrsim/PoleFigure.hpp +++ b/src/libmtrsim/PoleFigure.hpp @@ -1,6 +1,7 @@ #pragma once #include "ODFSampler.hpp" +#include "mtrsim_export.h" #include namespace mtrsim @@ -9,7 +10,7 @@ namespace mtrsim /** * @brief Pole figure data: projected (X, Y) coordinates and intensity values. */ -struct PoleFigureData +struct MTRSIM_EXPORT PoleFigureData { Eigen::VectorXd x; ///< Stereographic X coordinates Eigen::VectorXd y; ///< Stereographic Y coordinates @@ -24,7 +25,7 @@ struct PoleFigureData * to HexagonalOps::generatePoleFigure() for the stereographic * projection and intensity accumulation. */ -class PoleFigure +class MTRSIM_EXPORT PoleFigure { public: PoleFigure() = default; diff --git a/src/libmtrsim/QSimVN.cpp b/src/libmtrsim/QSimVN.cpp index 51471e5..0ddd082 100644 --- a/src/libmtrsim/QSimVN.cpp +++ b/src/libmtrsim/QSimVN.cpp @@ -273,8 +273,7 @@ QSimVN::ChlrdrResult QSimVN::chlrdr(const Eigen::MatrixXd& r, const Eigen::Vecto // mvndns — inner integrand density evaluation. // Port of nested function mvndns() in qsimvn.m. -double QSimVN::mvndns(int n, const Eigen::MatrixXd& ch, double ci, double dci, - const Eigen::VectorXd& x, const Eigen::VectorXd& a, const Eigen::VectorXd& b) const +double QSimVN::mvndns(int n, const Eigen::MatrixXd& ch, double ci, double dci, const Eigen::VectorXd& x, const Eigen::VectorXd& a, const Eigen::VectorXd& b) const { const double cn = 37.5; Eigen::VectorXd y = Eigen::VectorXd::Zero(n - 1); diff --git a/src/libmtrsim/QSimVN.hpp b/src/libmtrsim/QSimVN.hpp index a2c82a1..7ffa4e5 100644 --- a/src/libmtrsim/QSimVN.hpp +++ b/src/libmtrsim/QSimVN.hpp @@ -1,5 +1,6 @@ #pragma once +#include "mtrsim_export.h" #include #include #include @@ -20,7 +21,7 @@ namespace mtrsim * License note: the underlying algorithm is due to Alan Genz (BSD-style). * Full attribution is preserved in matlab/qsimvn.m. */ -class QSimVN +class MTRSIM_EXPORT QSimVN { public: /** @@ -37,10 +38,7 @@ class QSimVN * @param b Upper integration limits (length n, may be +inf) * @return {probability estimate, error estimate} */ - std::pair compute(int m, - const Eigen::MatrixXd& r, - const Eigen::VectorXd& a, - const Eigen::VectorXd& b); + std::pair compute(int m, const Eigen::MatrixXd& r, const Eigen::VectorXd& a, const Eigen::VectorXd& b); private: // Cholesky decomposition with reordering (chlrdr in original) @@ -51,15 +49,9 @@ class QSimVN Eigen::VectorXd bp; }; - ChlrdrResult chlrdr(const Eigen::MatrixXd& r, - const Eigen::VectorXd& a, - const Eigen::VectorXd& b) const; + ChlrdrResult chlrdr(const Eigen::MatrixXd& r, const Eigen::VectorXd& a, const Eigen::VectorXd& b) const; - double mvndns(int n, const Eigen::MatrixXd& ch, - double ci, double dci, - const Eigen::VectorXd& x, - const Eigen::VectorXd& a, - const Eigen::VectorXd& b) const; + double mvndns(int n, const Eigen::MatrixXd& ch, double ci, double dci, const Eigen::VectorXd& x, const Eigen::VectorXd& a, const Eigen::VectorXd& b) const; static double phi(double z); static double phiInv(double p); diff --git a/src/libmtrsim/SimulationParams.hpp b/src/libmtrsim/SimulationParams.hpp index ea5dc16..a95a0a1 100644 --- a/src/libmtrsim/SimulationParams.hpp +++ b/src/libmtrsim/SimulationParams.hpp @@ -1,5 +1,6 @@ #pragma once +#include "mtrsim_export.h" #include #include #include @@ -13,7 +14,7 @@ namespace mtrsim * * These map directly to the top-level parameter block in simulate_MTRs.m. */ -struct SimulationParams +struct MTRSIM_EXPORT SimulationParams { // Volume dimensions [mm] double xLen = 1.5 * 25.4; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 23fb009..a7a4fa7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,4 +1,5 @@ add_executable(mtrsim_tests + main.cpp test_placeholder.cpp test_qsimvn.cpp test_gp_generator.cpp @@ -14,7 +15,7 @@ target_include_directories(mtrsim_tests target_link_libraries(mtrsim_tests PRIVATE mtrsim::mtrsim - Catch2::Catch2WithMain + Catch2::Catch2 ) catch_discover_tests(mtrsim_tests) diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 0000000..4ed06df --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include diff --git a/tests/test_gp_generator.cpp b/tests/test_gp_generator.cpp index 360e5f1..4ca5ab8 100644 --- a/tests/test_gp_generator.cpp +++ b/tests/test_gp_generator.cpp @@ -2,15 +2,13 @@ #include "PGRFSimulation.hpp" #include "SimulationParams.hpp" -#include -#include #include +#include #include #include #include using namespace mtrsim; -using Catch::Approx; // ───────────────────────────────────────────────────────────────────────────── // GPGenerator tests @@ -19,10 +17,7 @@ using Catch::Approx; namespace { // Exponential correlation: rho(lag, theta) = exp(-|lag|/theta) -auto expCorrFn = [](double lag, double theta) -> double -{ - return std::exp(-std::abs(lag) / theta); -}; +auto expCorrFn = [](double lag, double theta) -> double { return std::exp(-std::abs(lag) / theta); }; } // anonymous namespace TEST_CASE("GPGenerator: output length equals nx*ny*nz", "[gpgenerator]") @@ -45,15 +40,13 @@ TEST_CASE("GPGenerator: 2D slab (nz=1) output length is nx*ny", "[gpgenerator]") TEST_CASE("GPGenerator: same seed produces identical output", "[gpgenerator]") { - auto gp1 = [&]() - { + auto gp1 = [&]() { std::mt19937_64 rng(999); GPGenerator gpGen(rng, expCorrFn); return gpGen.generate(0.1, 0.1, 0.1, {0.5, 0.5, 0.5}, 8, 8, 1); }(); - auto gp2 = [&]() - { + auto gp2 = [&]() { std::mt19937_64 rng(999); GPGenerator gpGen(rng, expCorrFn); return gpGen.generate(0.1, 0.1, 0.1, {0.5, 0.5, 0.5}, 8, 8, 1); @@ -137,7 +130,7 @@ TEST_CASE("PGRFSimulation: output dimensions are correct", "[pgrfsimulation]") const int nx = static_cast(std::round(params.xLen / params.dx)); // 5 const int ny = static_cast(std::round(params.yLen / params.dy)); // 5 const int nz = 1; - const int N = nx * ny * nz; // 25 + const int N = nx * ny * nz; // 25 const int numGaussians = static_cast(params.volumeFractions.size()) - 1; // 2 std::mt19937_64 rng(42); diff --git a/tests/test_ipf_mapper.cpp b/tests/test_ipf_mapper.cpp index 8ae0a1e..d2ae6ca 100644 --- a/tests/test_ipf_mapper.cpp +++ b/tests/test_ipf_mapper.cpp @@ -2,15 +2,13 @@ #include "ODFCalculator.hpp" #include "PoleFigure.hpp" -#include -#include #include +#include #include #include #include using namespace mtrsim; -using Catch::Approx; // ───────────────────────────────────────────────────────────────────────────── // IPFMapper tests @@ -22,7 +20,7 @@ TEST_CASE("IPFMapper::eulerToColors: output size equals input size", "[ipfmapper const int N = 15; Eigen::VectorXd phi1 = Eigen::VectorXd::LinSpaced(N, 0.0, 2.0 * std::numbers::pi); - Eigen::VectorXd phi = Eigen::VectorXd::LinSpaced(N, 0.0, std::numbers::pi); + Eigen::VectorXd phi = Eigen::VectorXd::LinSpaced(N, 0.0, std::numbers::pi); Eigen::VectorXd phi2 = Eigen::VectorXd::LinSpaced(N, 0.0, 2.0 * std::numbers::pi); const auto colors = mapper.eulerToColors(phi1, phi, phi2); @@ -38,7 +36,7 @@ TEST_CASE("IPFMapper::eulerToColors: all channel values in [0, 255]", "[ipfmappe for(int i = 0; i < N; ++i) { phi1[i] = 0.314159 * i; - phi[i] = 0.157080 * i; + phi[i] = 0.157080 * i; phi2[i] = 0.628318 * i; } @@ -61,7 +59,7 @@ TEST_CASE("IPFMapper::eulerToColors: identity orientation [0001] maps to red", " Eigen::VectorXd phi1(1), phi(1), phi2(1); phi1[0] = 0.0; - phi[0] = 0.0; + phi[0] = 0.0; phi2[0] = 1.0e-15; // small but non-zero to avoid atan2(0,0) edge case in stereographic denom const auto colors = mapper.eulerToColors(phi1, phi, phi2); @@ -80,7 +78,7 @@ TEST_CASE("IPFMapper::eulerToColors: same orientation gives identical colours", Eigen::VectorXd phi1(3), phi(3), phi2(3); phi1 << p1, p1, p1; - phi << ph, ph, ph; + phi << ph, ph, ph; phi2 << p2, p2, p2; const auto colors = mapper.eulerToColors(phi1, phi, phi2); @@ -110,7 +108,7 @@ TEST_CASE("IPFMapper::writePNG: creates file on disk", "[ipfmapper]") // 3×3 regular spatial grid const int nx = 3, ny = 3; - const int N = nx * ny; + const int N = nx * ny; Eigen::MatrixXd coords(N, 2); Eigen::VectorXd phi1(N), phi(N), phi2(N); @@ -121,9 +119,9 @@ TEST_CASE("IPFMapper::writePNG: creates file on disk", "[ipfmapper]") { coords(idx, 0) = static_cast(ix) * 0.1; coords(idx, 1) = static_cast(iy) * 0.1; - phi1[idx] = 0.2 * idx; - phi[idx] = 0.1 * idx; - phi2[idx] = 0.3 * idx; + phi1[idx] = 0.2 * idx; + phi[idx] = 0.1 * idx; + phi2[idx] = 0.3 * idx; ++idx; } } @@ -146,7 +144,7 @@ ODFComponent makeTinyODF() ODFCalculator calc; Eigen::VectorXd phi1(5), phi(5), phi2(5); phi1 << 0.3, 1.0, 2.0, 3.5, 5.0; - phi << 0.2, 0.5, 0.8, 1.1, 1.4; + phi << 0.2, 0.5, 0.8, 1.1, 1.4; phi2 << 0.6, 1.5, 2.5, 3.0, 4.5; return calc.compute(phi1, phi, phi2); } diff --git a/tests/test_odf_sampler.cpp b/tests/test_odf_sampler.cpp index f26609d..c29c1a0 100644 --- a/tests/test_odf_sampler.cpp +++ b/tests/test_odf_sampler.cpp @@ -2,16 +2,14 @@ #include "ODFCalculator.hpp" #include "ODFSampler.hpp" -#include -#include #include +#include #include #include #include #include using namespace mtrsim; -using Catch::Approx; // ───────────────────────────────────────────────────────────────────────────── // CrystalSymmetry tests @@ -35,7 +33,7 @@ TEST_CASE("CrystalSymmetry HCP: expand output dimensions are N*12 x 3", "[crysta const int N = 5; Eigen::VectorXd phi1 = Eigen::VectorXd::Constant(N, 0.1); - Eigen::VectorXd phi = Eigen::VectorXd::Constant(N, 0.5); + Eigen::VectorXd phi = Eigen::VectorXd::Constant(N, 0.5); Eigen::VectorXd phi2 = Eigen::VectorXd::Constant(N, 0.3); const Eigen::MatrixXd result = cs.expand(phi1, phi, phi2); @@ -55,7 +53,7 @@ TEST_CASE("CrystalSymmetry HCP: identity operator preserves orientation", "[crys Eigen::VectorXd phi1(1), phi(1), phi2(1); phi1 << p1; - phi << ph; + phi << ph; phi2 << p2; const Eigen::MatrixXd result = cs.expand(phi1, phi, phi2); @@ -73,7 +71,7 @@ TEST_CASE("CrystalSymmetry HCP: all output angles in valid range", "[crystalsymm // Use a handful of orientations with varied Euler angles Eigen::VectorXd phi1(3), phi(3), phi2(3); phi1 << 0.3, 1.5, 5.0; - phi << 0.1, 0.9, 1.5; + phi << 0.1, 0.9, 1.5; phi2 << 0.6, 2.0, 4.0; const Eigen::MatrixXd result = cs.expand(phi1, phi, phi2); @@ -84,11 +82,11 @@ TEST_CASE("CrystalSymmetry HCP: all output angles in valid range", "[crystalsymm for(int r = 0; r < result.rows(); ++r) { CHECK(result(r, 0) >= -1e-10); - CHECK(result(r, 0) <= twoPi + 1e-10); // phi1 ∈ [0, 2π) + CHECK(result(r, 0) <= twoPi + 1e-10); // phi1 ∈ [0, 2π) CHECK(result(r, 1) >= -1e-10); - CHECK(result(r, 1) <= piVal + 1e-10); // PHI ∈ [0, π] + CHECK(result(r, 1) <= piVal + 1e-10); // PHI ∈ [0, π] CHECK(result(r, 2) >= -1e-10); - CHECK(result(r, 2) <= twoPi + 1e-10); // phi2 ∈ [0, 2π) + CHECK(result(r, 2) <= twoPi + 1e-10); // phi2 ∈ [0, 2π) } } @@ -106,16 +104,16 @@ TEST_CASE("ODFCalculator: output ODFComponent has correct sizes", "[odfcalculato for(int i = 0; i < N; ++i) { phi1[i] = 0.1 * i; - phi[i] = 0.05 * i; + phi[i] = 0.05 * i; phi2[i] = 0.2 * i; } const ODFComponent odf = calc.compute(phi1, phi, phi2); const int expected = 72 * 36 * 72; // = 186624 - CHECK(odf.odfVal.size() == expected); + CHECK(odf.odfVal.size() == expected); CHECK(odf.phi1Bins.size() == expected); - CHECK(odf.phiBins.size() == expected); + CHECK(odf.phiBins.size() == expected); CHECK(odf.phi2Bins.size() == expected); } @@ -125,7 +123,7 @@ TEST_CASE("ODFCalculator: odfVal is non-negative", "[odfcalculator]") Eigen::VectorXd phi1(3), phi(3), phi2(3); phi1 << 0.5, 1.0, 2.0; - phi << 0.3, 0.6, 1.2; + phi << 0.3, 0.6, 1.2; phi2 << 1.0, 2.0, 3.0; const ODFComponent odf = calc.compute(phi1, phi, phi2); @@ -139,7 +137,7 @@ TEST_CASE("ODFCalculator: bin centres are within Euler-space range", "[odfcalcul Eigen::VectorXd phi1(2), phi(2), phi2(2); phi1 << 0.0, 1.0; - phi << 0.0, 0.8; + phi << 0.0, 0.8; phi2 << 0.0, 2.0; const ODFComponent odf = calc.compute(phi1, phi, phi2); @@ -168,7 +166,7 @@ TEST_CASE("ODFCalculator: sum of odfVal ≈ 1 for large grid", "[odfcalculator]" for(int i = 0; i < N; ++i) { phi1[i] = 2.0 * std::numbers::pi * (static_cast(i) / N); - phi[i] = std::numbers::pi * (static_cast(i) / N); + phi[i] = std::numbers::pi * (static_cast(i) / N); phi2[i] = 2.0 * std::numbers::pi * (static_cast((i * 37) % N) / N); } @@ -189,9 +187,9 @@ namespace ODFComponent makeUniformODF(int nBins, double binWidth) { ODFComponent c; - c.odfVal = Eigen::VectorXd::Ones(nBins) / static_cast(nBins); + c.odfVal = Eigen::VectorXd::Ones(nBins) / static_cast(nBins); c.phi1Bins = Eigen::VectorXd::LinSpaced(nBins, 0.5 * binWidth, (nBins - 0.5) * binWidth); - c.phiBins = c.phi1Bins; + c.phiBins = c.phi1Bins; c.phi2Bins = c.phi1Bins; return c; } @@ -239,8 +237,8 @@ TEST_CASE("ODFSampler::sampleN: uniform ODF samples cover bin centres uniformly std::mt19937_64 rng(12345); ODFSampler sampler(rng); - const int nBins = 10; - const double bw = 2.0 * std::numbers::pi / nBins; + const int nBins = 10; + const double bw = 2.0 * std::numbers::pi / nBins; const ODFComponent uODF = makeUniformODF(nBins, bw); const int N = 10000; @@ -251,7 +249,7 @@ TEST_CASE("ODFSampler::sampleN: uniform ODF samples cover bin centres uniformly for(int i = 0; i < N; ++i) { int bin = static_cast(std::floor(result(i, 0) / bw)); - bin = std::clamp(bin, 0, nBins - 1); + bin = std::clamp(bin, 0, nBins - 1); counts[bin]++; } @@ -276,6 +274,6 @@ TEST_CASE("ODFSampler::sampleOne: returns a single valid orientation", "[odfsamp // Angles should be near the bin centres ± half a bin width CHECK(ea.phi1 >= -bw); - CHECK(ea.phi >= -bw); + CHECK(ea.phi >= -bw); CHECK(ea.phi2 >= -bw); } diff --git a/tests/test_placeholder.cpp b/tests/test_placeholder.cpp index 8f7b55b..eb20beb 100644 --- a/tests/test_placeholder.cpp +++ b/tests/test_placeholder.cpp @@ -1,4 +1,4 @@ -#include +#include // Placeholder test — replaced with real tests as each module is implemented. diff --git a/tests/test_qsimvn.cpp b/tests/test_qsimvn.cpp index e40e808..5312bfa 100644 --- a/tests/test_qsimvn.cpp +++ b/tests/test_qsimvn.cpp @@ -1,15 +1,13 @@ #include "AssignmentRule.hpp" #include "QSimVN.hpp" -#include -#include #include +#include #include #include #include using namespace mtrsim; -using Catch::Approx; // ───────────────────────────────────────────────────────────────────────────── // QSimVN tests @@ -68,16 +66,10 @@ TEST_CASE("QSimVN: bivariate correlated N(0,R) matches known probability", "[qsi QSimVN qsimvn(rng); Eigen::MatrixXd r(4, 4); - r << 4, 3, 2, 1, - 3, 5, -1, 1, - 2, -1, 4, 2, - 1, 1, 2, 5; + r << 4, 3, 2, 1, 3, 5, -1, 1, 2, -1, 4, 2, 1, 1, 2, 5; Eigen::VectorXd a(4), b(4); - a << -std::numeric_limits::infinity(), - -std::numeric_limits::infinity(), - -std::numeric_limits::infinity(), - -std::numeric_limits::infinity(); + a << -std::numeric_limits::infinity(), -std::numeric_limits::infinity(), -std::numeric_limits::infinity(), -std::numeric_limits::infinity(); b << 1.0, 2.0, 3.0, 4.0; auto [p, e] = qsimvn.compute(5000, r, a, b); @@ -160,10 +152,10 @@ TEST_CASE("AssignmentRule::evaluate: 2 Gaussians, simple box assignment", "[assi // Z matrix: 4 test voxels Eigen::MatrixXd z(4, 2); - z << -1.0, 1.0, // z1<0 → comp 0 (1-based: 1) - 1.0, -1.0, // z1>=0, z2<0 → comp 1 (1-based: 2) - 1.0, 1.0, // z1>=0, z2>=0 → comp 2 (1-based: 3) - -0.5, -0.5; // z1<0 → comp 0 (1-based: 1) + z << -1.0, 1.0, // z1<0 → comp 0 (1-based: 1) + 1.0, -1.0, // z1>=0, z2<0 → comp 1 (1-based: 2) + 1.0, 1.0, // z1>=0, z2>=0 → comp 2 (1-based: 3) + -0.5, -0.5; // z1<0 → comp 0 (1-based: 1) std::mt19937_64 rng(0); AssignmentRule ar(rng); diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json new file mode 100644 index 0000000..83ffd66 --- /dev/null +++ b/vcpkg-configuration.json @@ -0,0 +1,30 @@ +{ + "default-registry": { + "kind": "git", + "repository": "https://github.com/microsoft/vcpkg", + "baseline": "8eb57355a4ffb410a2e94c07b4dca2dffbee8e50" + }, + "registries": [ + { + "kind": "git", + "repository": "https://github.com/bluequartzsoftware/simplnx-registry", + "baseline": "fc1e12b6502374fdda171a1df44d8e3f3b61e84c", + "packages": [ + "catch2", + "ebsdlib", + "eigen3", + "expected-lite", + "fmt", + "h5support", + "hdf5", + "nlohmann-json", + "spdlog", + "tbb", + "vcpkg-cmake-config", + "vcpkg-cmake", + "zlib", + "zstd" + ] + } + ] +} diff --git a/vcpkg.json b/vcpkg.json index ae9196d..e6e2b7f 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -24,7 +24,15 @@ }, { "name": "spdlog" + }, + { + "name": "ebsdlib", + "version>=": "2.1.0" + }, + { + "name": "h5support" } + ], "features": { "tests": {