├── .clang-format ├── .clang-tidy ├── .codespellrc ├── .envrc ├── .github └── workflows │ ├── build-linux.yml │ ├── build-macos.yml │ └── ci.yml ├── .gitignore ├── BUILDING.md ├── CMakeLists.txt ├── CMakePresets.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── HACKING.md ├── LICENSE ├── README.md ├── cmake ├── CPM.cmake ├── coverage.cmake ├── dev-mode.cmake ├── docs.cmake ├── install-config.cmake ├── install-rules.cmake ├── lint-targets.cmake ├── lint.cmake ├── open-cpp-coverage.cmake.example ├── prelude.cmake ├── project-is-top-level.cmake ├── spell-targets.cmake ├── spell.cmake ├── variables.cmake └── windows-set-path.cmake ├── docs ├── Doxyfile ├── doxygen-awesome-darkmode-toggle.js ├── doxygen-awesome-fragment-copy-button.js ├── doxygen-awesome-interactive-toc.js ├── doxygen-awesome-paragraph-link.js ├── doxygen-awesome-sidebar-only-darkmode-toggle.css ├── doxygen-awesome-sidebar-only.css ├── doxygen-awesome-tabs.js ├── doxygen-awesome.css ├── header.html ├── index.md └── layout.xml ├── flake.lock ├── flake.nix ├── include └── vstat │ ├── bivariate.hpp │ ├── combine.hpp │ ├── univariate.hpp │ ├── util.hpp │ └── vstat.hpp ├── logo └── vstat.svg ├── pyproject.toml ├── setup.py ├── src └── vstat.cpp ├── test ├── CMakeLists.txt ├── benchmarks │ ├── cov_double.png │ ├── cov_float.png │ ├── var_double.png │ └── var_float.png ├── cmake │ ├── doctest.cmake │ └── doctestAddTests.cmake └── source │ ├── nanobench.h │ ├── stat_other.hpp │ └── vstat_test.cpp └── vstat └── __init__.py /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: Chromium 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveMacros: false 7 | AlignConsecutiveAssignments: false 8 | AlignConsecutiveBitFields: false 9 | AlignConsecutiveDeclarations: false 10 | AlignEscapedNewlines: DontAlign 11 | AlignOperands: DontAlign 12 | AlignTrailingComments: false 13 | AllowAllArgumentsOnNextLine: true 14 | AllowAllConstructorInitializersOnNextLine: false 15 | AllowAllParametersOfDeclarationOnNextLine: true 16 | AllowShortEnumsOnASingleLine: false 17 | AllowShortBlocksOnASingleLine: Empty 18 | AllowShortCaseLabelsOnASingleLine: false 19 | AllowShortFunctionsOnASingleLine: Inline 20 | AllowShortLambdasOnASingleLine: All 21 | AllowShortIfStatementsOnASingleLine: Never 22 | AllowShortLoopsOnASingleLine: false 23 | AlwaysBreakAfterDefinitionReturnType: None 24 | AlwaysBreakAfterReturnType: None 25 | AlwaysBreakBeforeMultilineStrings: true 26 | AlwaysBreakTemplateDeclarations: Yes 27 | BinPackArguments: false 28 | BinPackParameters: false 29 | BraceWrapping: 30 | AfterCaseLabel: false 31 | AfterClass: true 32 | AfterControlStatement: MultiLine 33 | AfterEnum: true 34 | AfterFunction: true 35 | AfterNamespace: true 36 | AfterObjCDeclaration: false 37 | AfterStruct: true 38 | AfterUnion: true 39 | AfterExternBlock: true 40 | BeforeCatch: false 41 | BeforeElse: false 42 | BeforeLambdaBody: true 43 | BeforeWhile: false 44 | IndentBraces: false 45 | SplitEmptyFunction: true 46 | SplitEmptyRecord: true 47 | SplitEmptyNamespace: true 48 | BreakBeforeBinaryOperators: NonAssignment 49 | BreakBeforeBraces: Custom 50 | # BreakBeforeInheritanceComma: true 51 | BreakInheritanceList: BeforeComma 52 | BreakBeforeTernaryOperators: true 53 | BreakConstructorInitializersBeforeComma: true 54 | BreakConstructorInitializers: BeforeComma 55 | BreakAfterJavaFieldAnnotations: true 56 | BreakStringLiterals: true 57 | ColumnLimit: 80 58 | CommentPragmas: '^ IWYU pragma:' 59 | CompactNamespaces: false 60 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 61 | ConstructorInitializerIndentWidth: 4 62 | ContinuationIndentWidth: 4 63 | Cpp11BracedListStyle: true 64 | DeriveLineEnding: false 65 | DerivePointerAlignment: false 66 | DisableFormat: false 67 | ExperimentalAutoDetectBinPacking: false 68 | FixNamespaceComments: true 69 | ForEachMacros: 70 | - foreach 71 | - Q_FOREACH 72 | - BOOST_FOREACH 73 | IncludeBlocks: Regroup 74 | IncludeCategories: 75 | # Standard library headers come before anything else 76 | - Regex: '^<[a-z_]+>' 77 | Priority: -1 78 | - Regex: '^<.+\.h(pp)?>' 79 | Priority: 1 80 | - Regex: '^<.*' 81 | Priority: 2 82 | - Regex: '.*' 83 | Priority: 3 84 | IncludeIsMainRegex: '' 85 | IncludeIsMainSourceRegex: '' 86 | IndentCaseLabels: true 87 | IndentCaseBlocks: false 88 | IndentGotoLabels: true 89 | IndentPPDirectives: AfterHash 90 | IndentExternBlock: NoIndent 91 | IndentWidth: 2 92 | IndentWrappedFunctionNames: false 93 | InsertTrailingCommas: Wrapped 94 | JavaScriptQuotes: Double 95 | JavaScriptWrapImports: true 96 | KeepEmptyLinesAtTheStartOfBlocks: false 97 | MacroBlockBegin: '' 98 | MacroBlockEnd: '' 99 | MaxEmptyLinesToKeep: 1 100 | NamespaceIndentation: None 101 | ObjCBinPackProtocolList: Never 102 | ObjCBlockIndentWidth: 2 103 | ObjCBreakBeforeNestedBlockParam: true 104 | ObjCSpaceAfterProperty: false 105 | ObjCSpaceBeforeProtocolList: true 106 | PenaltyBreakAssignment: 2 107 | PenaltyBreakBeforeFirstCallParameter: 1 108 | PenaltyBreakComment: 300 109 | PenaltyBreakFirstLessLess: 120 110 | PenaltyBreakString: 1000 111 | PenaltyBreakTemplateDeclaration: 10 112 | PenaltyExcessCharacter: 1000000 113 | PenaltyReturnTypeOnItsOwnLine: 200 114 | PointerAlignment: Left 115 | RawStringFormats: 116 | - Language: Cpp 117 | Delimiters: 118 | - cc 119 | - CC 120 | - cpp 121 | - Cpp 122 | - CPP 123 | - 'c++' 124 | - 'C++' 125 | CanonicalDelimiter: '' 126 | BasedOnStyle: google 127 | - Language: TextProto 128 | Delimiters: 129 | - pb 130 | - PB 131 | - proto 132 | - PROTO 133 | EnclosingFunctions: 134 | - EqualsProto 135 | - EquivToProto 136 | - PARSE_PARTIAL_TEXT_PROTO 137 | - PARSE_TEST_PROTO 138 | - PARSE_TEXT_PROTO 139 | - ParseTextOrDie 140 | - ParseTextProtoOrDie 141 | - ParseTestProto 142 | - ParsePartialTestProto 143 | CanonicalDelimiter: '' 144 | BasedOnStyle: google 145 | ReflowComments: true 146 | SortIncludes: true 147 | SortUsingDeclarations: true 148 | SpaceAfterCStyleCast: false 149 | SpaceAfterLogicalNot: false 150 | SpaceAfterTemplateKeyword: false 151 | SpaceBeforeAssignmentOperators: true 152 | SpaceBeforeCpp11BracedList: true 153 | SpaceBeforeCtorInitializerColon: true 154 | SpaceBeforeInheritanceColon: true 155 | SpaceBeforeParens: ControlStatementsExceptForEachMacros 156 | SpaceBeforeRangeBasedForLoopColon: true 157 | SpaceInEmptyBlock: false 158 | SpaceInEmptyParentheses: false 159 | SpacesBeforeTrailingComments: 2 160 | SpacesInAngles: false 161 | SpacesInConditionalStatement: false 162 | SpacesInContainerLiterals: false 163 | SpacesInCStyleCastParentheses: false 164 | SpacesInParentheses: false 165 | SpacesInSquareBrackets: false 166 | SpaceBeforeSquareBrackets: false 167 | Standard: Auto 168 | StatementMacros: 169 | - Q_UNUSED 170 | - QT_REQUIRE_VERSION 171 | TabWidth: 8 172 | UseCRLF: false 173 | UseTab: Never 174 | WhitespaceSensitiveMacros: 175 | - STRINGIZE 176 | - PP_STRINGIZE 177 | - BOOST_PP_STRINGIZE 178 | ... 179 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | # Enable ALL the things! Except not really 3 | # misc-non-private-member-variables-in-classes: the options don't do anything 4 | Checks: "*,\ 5 | -readability-identifier-length,\ 6 | -google-readability-todo,\ 7 | -altera-unroll-loops,\ 8 | -fuchsia-*,\ 9 | fuchsia-multiple-inheritance,\ 10 | -llvm-header-guard,\ 11 | -llvm-include-order,\ 12 | -llvmlibc-*,\ 13 | -misc-non-private-member-variables-in-classes" 14 | WarningsAsErrors: '' 15 | CheckOptions: 16 | - key: 'bugprone-argument-comment.StrictMode' 17 | value: 'true' 18 | # Prefer using enum classes with 2 values for parameters instead of bools 19 | - key: 'bugprone-argument-comment.CommentBoolLiterals' 20 | value: 'true' 21 | - key: 'bugprone-misplaced-widening-cast.CheckImplicitCasts' 22 | value: 'true' 23 | - key: 'bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression' 24 | value: 'true' 25 | - key: 'bugprone-suspicious-string-compare.WarnOnLogicalNotComparison' 26 | value: 'true' 27 | - key: 'readability-simplify-boolean-expr.ChainedConditionalReturn' 28 | value: 'true' 29 | - key: 'readability-simplify-boolean-expr.ChainedConditionalAssignment' 30 | value: 'true' 31 | - key: 'readability-uniqueptr-delete-release.PreferResetCall' 32 | value: 'true' 33 | - key: 'cppcoreguidelines-init-variables.MathHeader' 34 | value: '' 35 | - key: 'cppcoreguidelines-narrowing-conversions.PedanticMode' 36 | value: 'true' 37 | - key: 'readability-else-after-return.WarnOnUnfixable' 38 | value: 'true' 39 | - key: 'readability-else-after-return.WarnOnConditionVariables' 40 | value: 'true' 41 | - key: 'readability-inconsistent-declaration-parameter-name.Strict' 42 | value: 'true' 43 | - key: 'readability-qualified-auto.AddConstToQualified' 44 | value: 'true' 45 | - key: 'readability-redundant-access-specifiers.CheckFirstDeclaration' 46 | value: 'true' 47 | # These seem to be the most common identifier styles 48 | - key: 'readability-identifier-naming.AbstractClassCase' 49 | value: 'lower_case' 50 | - key: 'readability-identifier-naming.ClassCase' 51 | value: 'lower_case' 52 | - key: 'readability-identifier-naming.ClassConstantCase' 53 | value: 'lower_case' 54 | - key: 'readability-identifier-naming.ClassMemberCase' 55 | value: 'lower_case' 56 | - key: 'readability-identifier-naming.ClassMethodCase' 57 | value: 'lower_case' 58 | - key: 'readability-identifier-naming.ConstantCase' 59 | value: 'lower_case' 60 | - key: 'readability-identifier-naming.ConstantMemberCase' 61 | value: 'lower_case' 62 | - key: 'readability-identifier-naming.ConstantParameterCase' 63 | value: 'lower_case' 64 | - key: 'readability-identifier-naming.ConstantPointerParameterCase' 65 | value: 'lower_case' 66 | - key: 'readability-identifier-naming.ConstexprFunctionCase' 67 | value: 'lower_case' 68 | - key: 'readability-identifier-naming.ConstexprMethodCase' 69 | value: 'lower_case' 70 | - key: 'readability-identifier-naming.ConstexprVariableCase' 71 | value: 'lower_case' 72 | - key: 'readability-identifier-naming.EnumCase' 73 | value: 'lower_case' 74 | - key: 'readability-identifier-naming.EnumConstantCase' 75 | value: 'lower_case' 76 | - key: 'readability-identifier-naming.FunctionCase' 77 | value: 'lower_case' 78 | - key: 'readability-identifier-naming.GlobalConstantCase' 79 | value: 'lower_case' 80 | - key: 'readability-identifier-naming.GlobalConstantPointerCase' 81 | value: 'lower_case' 82 | - key: 'readability-identifier-naming.GlobalFunctionCase' 83 | value: 'lower_case' 84 | - key: 'readability-identifier-naming.GlobalPointerCase' 85 | value: 'lower_case' 86 | - key: 'readability-identifier-naming.GlobalVariableCase' 87 | value: 'lower_case' 88 | - key: 'readability-identifier-naming.InlineNamespaceCase' 89 | value: 'lower_case' 90 | - key: 'readability-identifier-naming.LocalConstantCase' 91 | value: 'lower_case' 92 | - key: 'readability-identifier-naming.LocalConstantPointerCase' 93 | value: 'lower_case' 94 | - key: 'readability-identifier-naming.LocalPointerCase' 95 | value: 'lower_case' 96 | - key: 'readability-identifier-naming.LocalVariableCase' 97 | value: 'lower_case' 98 | - key: 'readability-identifier-naming.MacroDefinitionCase' 99 | value: 'UPPER_CASE' 100 | - key: 'readability-identifier-naming.MemberCase' 101 | value: 'lower_case' 102 | - key: 'readability-identifier-naming.MethodCase' 103 | value: 'lower_case' 104 | - key: 'readability-identifier-naming.NamespaceCase' 105 | value: 'lower_case' 106 | - key: 'readability-identifier-naming.ParameterCase' 107 | value: 'lower_case' 108 | - key: 'readability-identifier-naming.ParameterPackCase' 109 | value: 'lower_case' 110 | - key: 'readability-identifier-naming.PointerParameterCase' 111 | value: 'lower_case' 112 | - key: 'readability-identifier-naming.PrivateMemberCase' 113 | value: 'lower_case' 114 | - key: 'readability-identifier-naming.PrivateMemberPrefix' 115 | value: '' 116 | - key: 'readability-identifier-naming.PrivateMethodCase' 117 | value: 'lower_case' 118 | - key: 'readability-identifier-naming.ProtectedMemberCase' 119 | value: 'lower_case' 120 | - key: 'readability-identifier-naming.ProtectedMemberPrefix' 121 | value: 'm_' 122 | - key: 'readability-identifier-naming.ProtectedMethodCase' 123 | value: 'lower_case' 124 | - key: 'readability-identifier-naming.PublicMemberCase' 125 | value: 'lower_case' 126 | - key: 'readability-identifier-naming.PublicMethodCase' 127 | value: 'lower_case' 128 | - key: 'readability-identifier-naming.ScopedEnumConstantCase' 129 | value: 'lower_case' 130 | - key: 'readability-identifier-naming.StaticConstantCase' 131 | value: 'lower_case' 132 | - key: 'readability-identifier-naming.StaticVariableCase' 133 | value: 'lower_case' 134 | - key: 'readability-identifier-naming.StructCase' 135 | value: 'lower_case' 136 | - key: 'readability-identifier-naming.TemplateParameterCase' 137 | value: 'CamelCase' 138 | - key: 'readability-identifier-naming.TemplateTemplateParameterCase' 139 | value: 'CamelCase' 140 | - key: 'readability-identifier-naming.TypeAliasCase' 141 | value: 'lower_case' 142 | - key: 'readability-identifier-naming.TypedefCase' 143 | value: 'lower_case' 144 | - key: 'readability-identifier-naming.TypeTemplateParameterCase' 145 | value: 'CamelCase' 146 | - key: 'readability-identifier-naming.UnionCase' 147 | value: 'lower_case' 148 | - key: 'readability-identifier-naming.ValueTemplateParameterCase' 149 | value: 'CamelCase' 150 | - key: 'readability-identifier-naming.VariableCase' 151 | value: 'lower_case' 152 | - key: 'readability-identifier-naming.VirtualMethodCase' 153 | value: 'lower_case' 154 | ... 155 | -------------------------------------------------------------------------------- /.codespellrc: -------------------------------------------------------------------------------- 1 | [codespell] 2 | builtin = clear,rare,en-GB_to_en-US,names,informal,code 3 | check-filenames = 4 | check-hidden = 5 | skip = */.git,*/build 6 | quiet-level = 2 7 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | use flake . --builders '' 2 | -------------------------------------------------------------------------------- /.github/workflows/build-linux.yml: -------------------------------------------------------------------------------- 1 | name: build-linux 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Release 12 | 13 | jobs: 14 | build-python-module: 15 | runs-on: ubuntu-24.04 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Build wheel in venv 20 | shell: bash -l {0} 21 | run: | 22 | python -m venv vstat-env 23 | source vstat-env/bin/activate 24 | pip install ninja nanobind scikit-build 25 | export CC=gcc 26 | export CXX=g++ 27 | python setup.py bdist_wheel 28 | deactivate 29 | 30 | -------------------------------------------------------------------------------- /.github/workflows/build-macos.yml: -------------------------------------------------------------------------------- 1 | name: build-macos 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Release 12 | 13 | jobs: 14 | build-python-module: 15 | runs-on: macos-14 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Build wheel in venv 20 | shell: bash -l {0} 21 | run: | 22 | python -m venv vstat-env 23 | source vstat-env/bin/activate 24 | pip install ninja nanobind scikit-build 25 | export CC=$(brew --prefix llvm@15)/bin/clang 26 | export CXX=$(brew --prefix llvm@15)/bin/clang++ 27 | python setup.py bdist_wheel 28 | deactivate 29 | 30 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | docs: 14 | # Deploy docs only when builds succeed 15 | runs-on: ubuntu-latest 16 | 17 | if: github.ref == 'refs/heads/main' 18 | && github.event_name == 'push' 19 | && github.repository_owner == 'heal-research' 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | 24 | - name: Install deps 25 | run: sudo apt-get update -q 26 | && sudo apt-get install doxygen graphviz -q -y 27 | 28 | - name: Build docs 29 | run: cmake -B build -D "CMAKE_PROJECT_INCLUDE=$PWD/cmake/docs.cmake" 30 | && cmake --build build --target docs 31 | 32 | - name: Deploy docs 33 | uses: peaceiris/actions-gh-pages@v4 34 | with: 35 | github_token: ${{ secrets.GITHUB_TOKEN }} 36 | publish_dir: ./docs/html 37 | allow_empty_commit: true 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vs/ 3 | .vscode/ 4 | .cache/ 5 | .clangd/ 6 | build/ 7 | cmake/open-cpp-coverage.cmake 8 | cmake-build-*/ 9 | prefix/ 10 | CMakeLists.txt.user 11 | CMakeUserPresets.json 12 | project-include-after.cmake 13 | .direnv 14 | -------------------------------------------------------------------------------- /BUILDING.md: -------------------------------------------------------------------------------- 1 | # Building with CMake 2 | 3 | ## Build 4 | 5 | This project doesn't require any special command-line flags to build to keep 6 | things simple. 7 | 8 | Here are the steps for building in release mode with a single-configuration 9 | generator, like the Unix Makefiles one: 10 | 11 | ```sh 12 | cmake -S . -B build -D CMAKE_BUILD_TYPE=Release 13 | cmake --build build 14 | ``` 15 | 16 | Here are the steps for building in release mode with a multi-configuration 17 | generator, like the Visual Studio ones: 18 | 19 | ```sh 20 | cmake -S . -B build 21 | cmake --build build --config Release 22 | ``` 23 | 24 | ## Install 25 | 26 | This project doesn't require any special command-line flags to install to keep 27 | things simple. As a prerequisite, the project has to be built with the above 28 | commands already. 29 | 30 | The below commands require at least CMake 3.15 to run, because that is the 31 | version in which [Install a Project][1] was added. 32 | 33 | Here is the command for installing the release mode artifacts with a 34 | single-configuration generator, like the Unix Makefiles one: 35 | 36 | ```sh 37 | cmake --install build 38 | ``` 39 | 40 | Here is the command for installing the release mode artifacts with a 41 | multi-configuration generator, like the Visual Studio ones: 42 | 43 | ```sh 44 | cmake --install build --config Release 45 | ``` 46 | 47 | [1]: https://cmake.org/cmake/help/latest/manual/cmake.1.html#install-a-project 48 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | include(cmake/prelude.cmake) 4 | 5 | project( 6 | vstat 7 | VERSION 1.0.0 8 | DESCRIPTION "C++17 library of computationally efficient methods for calculating sample statistics (mean, variance, covariance, correlation)." 9 | HOMEPAGE_URL "https://github.com/heal-research/vstat" 10 | LANGUAGES CXX 11 | ) 12 | 13 | set(CMAKE_CXX_STANDARD 20) 14 | set(CMAKE_CXX_STANDARD_REQUIRED True) 15 | 16 | docs_early_return() 17 | 18 | include(cmake/CPM.cmake) 19 | include(cmake/project-is-top-level.cmake) 20 | include(cmake/variables.cmake) 21 | 22 | # ---- Declare library ---- 23 | add_library(vstat_vstat INTERFACE) 24 | add_library(vstat::vstat ALIAS vstat_vstat) 25 | 26 | set_property( 27 | TARGET vstat_vstat PROPERTY 28 | EXPORT_NAME vstat 29 | ) 30 | 31 | target_include_directories( 32 | vstat_vstat ${vstat_warning_guard} 33 | INTERFACE 34 | "$" 35 | ) 36 | target_compile_features(vstat_vstat INTERFACE cxx_std_20) 37 | 38 | # ---- Dependencies ---- 39 | CPMAddPackage( 40 | NAME eve 41 | VERSION 2023.02.15 42 | GITHUB_REPOSITORY jfalcou/eve 43 | OPTIONS "EVE_BUILD_TEST OFF" 44 | ) 45 | target_link_libraries(vstat_vstat INTERFACE eve::eve) 46 | 47 | if (NOT VSTAT_NAMESPACE) 48 | set(VSTAT_NAMESPACE vstat) 49 | endif() 50 | message(STATUS "vstat namespace defined as '${VSTAT_NAMESPACE}'") 51 | 52 | # ---- Python module ---- 53 | if(vstat_BUILD_PYTHON) 54 | find_package(Python COMPONENTS Interpreter Development.Module REQUIRED) 55 | execute_process( 56 | COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir 57 | OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE NB_DIR) 58 | list(APPEND CMAKE_PREFIX_PATH "${NB_DIR}") 59 | find_package(nanobind CONFIG REQUIRED) 60 | 61 | nanobind_add_module(vstat_python ${PROJECT_SOURCE_DIR}/src/vstat.cpp) 62 | target_link_libraries(vstat_python PRIVATE vstat::vstat) 63 | set_target_properties(vstat_python PROPERTIES OUTPUT_NAME "vstat") 64 | endif() 65 | 66 | # ---- Install rules ---- 67 | 68 | if(NOT CMAKE_SKIP_INSTALL_RULES) 69 | include(cmake/install-rules.cmake) 70 | endif() 71 | 72 | # ---- Developer mode ---- 73 | if(NOT vstat_DEVELOPER_MODE) 74 | return() 75 | elseif(NOT PROJECT_IS_TOP_LEVEL) 76 | message(AUTHOR_WARNING "Developer mode is intended for developers of vstat") 77 | endif() 78 | 79 | include(cmake/dev-mode.cmake) 80 | 81 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 6, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 14, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "cmake-pedantic", 11 | "hidden": true, 12 | "warnings": { 13 | "dev": true, 14 | "deprecated": true, 15 | "uninitialized": true, 16 | "unusedCli": true, 17 | "systemVars": false 18 | }, 19 | "errors": { 20 | "dev": true, 21 | "deprecated": true 22 | } 23 | }, 24 | { 25 | "name": "dev-mode", 26 | "hidden": true, 27 | "inherits": "cmake-pedantic", 28 | "cacheVariables": { 29 | "vstat_DEVELOPER_MODE": "ON" 30 | } 31 | }, 32 | { 33 | "name": "cppcheck", 34 | "hidden": true, 35 | "cacheVariables": { 36 | "CMAKE_CXX_CPPCHECK": "cppcheck;--inline-suppr" 37 | } 38 | }, 39 | { 40 | "name": "clang-tidy", 41 | "hidden": true, 42 | "cacheVariables": { 43 | "CMAKE_CXX_CLANG_TIDY": "clang-tidy;--header-filter=${sourceDir}/*" 44 | } 45 | }, 46 | { 47 | "name": "ci-std", 48 | "description": "This preset makes sure the project actually builds with at least the specified standard", 49 | "hidden": true, 50 | "cacheVariables": { 51 | "CMAKE_CXX_EXTENSIONS": "OFF", 52 | "CMAKE_CXX_STANDARD": "17", 53 | "CMAKE_CXX_STANDARD_REQUIRED": "ON" 54 | } 55 | }, 56 | { 57 | "name": "flags-unix", 58 | "hidden": true, 59 | "cacheVariables": { 60 | "CMAKE_CXX_FLAGS": "-Wall -Wextra -Wpedantic" 61 | } 62 | }, 63 | { 64 | "name": "flags-windows", 65 | "hidden": true, 66 | "cacheVariables": { 67 | "CMAKE_CXX_FLAGS": "/W4 /permissive- /utf-8 /volatile:iso /EHsc /Zc:__cplusplus /Zc:throwingNew" 68 | } 69 | }, 70 | { 71 | "name": "ci-unix", 72 | "generator": "Unix Makefiles", 73 | "hidden": true, 74 | "inherits": ["flags-unix", "ci-std"], 75 | "cacheVariables": { 76 | "CMAKE_BUILD_TYPE": "Release" 77 | } 78 | }, 79 | { 80 | "name": "ci-win64", 81 | "inherits": ["flags-windows", "ci-std"], 82 | "generator": "Visual Studio 16 2019", 83 | "architecture": "x64", 84 | "hidden": true 85 | }, 86 | { 87 | "name": "coverage-unix", 88 | "binaryDir": "${sourceDir}/build/coverage", 89 | "inherits": "ci-unix", 90 | "hidden": true, 91 | "cacheVariables": { 92 | "ENABLE_COVERAGE": "ON", 93 | "CMAKE_BUILD_TYPE": "Coverage", 94 | "CMAKE_CXX_FLAGS_COVERAGE": "-Og -g --coverage -fkeep-inline-functions -fkeep-static-functions", 95 | "CMAKE_EXE_LINKER_FLAGS_COVERAGE": "--coverage", 96 | "CMAKE_SHARED_LINKER_FLAGS_COVERAGE": "--coverage" 97 | } 98 | }, 99 | { 100 | "name": "ci-coverage", 101 | "inherits": ["coverage-unix", "dev-mode"], 102 | "cacheVariables": { 103 | "COVERAGE_HTML_COMMAND": "" 104 | } 105 | }, 106 | { 107 | "name": "ci-sanitize", 108 | "binaryDir": "${sourceDir}/build/sanitize", 109 | "inherits": ["ci-unix", "dev-mode"], 110 | "cacheVariables": { 111 | "CMAKE_BUILD_TYPE": "Sanitize", 112 | "CMAKE_CXX_FLAGS_SANITIZE": "-O2 -g -fsanitize=address,undefined -fno-omit-frame-pointer -fno-common" 113 | } 114 | }, 115 | { 116 | "name": "ci-build", 117 | "binaryDir": "${sourceDir}/build", 118 | "hidden": true 119 | }, 120 | { 121 | "name": "ci-macos", 122 | "inherits": ["ci-build", "ci-unix", "dev-mode"] 123 | }, 124 | { 125 | "name": "ci-ubuntu", 126 | "inherits": ["ci-build", "ci-unix", "clang-tidy", "cppcheck", "dev-mode"] 127 | }, 128 | { 129 | "name": "ci-windows", 130 | "inherits": ["ci-build", "ci-win64", "dev-mode"] 131 | } 132 | ] 133 | } 134 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | * You will be judged by your contributions first, and your sense of humor 4 | second. 5 | * Nobody owes you anything. 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | 7 | 8 | ## Code of Conduct 9 | 10 | Please see the [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) document. 11 | 12 | ## Getting started 13 | 14 | Helpful notes for developers can be found in the [`HACKING.md`](HACKING.md) 15 | document. 16 | 17 | In addition to he above, if you use the presets file as instructed, then you 18 | should NOT check it into source control, just as the CMake documentation 19 | suggests. 20 | -------------------------------------------------------------------------------- /HACKING.md: -------------------------------------------------------------------------------- 1 | # Hacking 2 | 3 | Here is some wisdom to help you build and test this project as a developer and 4 | potential contributor. 5 | 6 | If you plan to contribute, please read the [CONTRIBUTING](CONTRIBUTING.md) 7 | guide. 8 | 9 | ## Developer mode 10 | 11 | Build system targets that are only useful for developers of this project are 12 | hidden if the `vstat_DEVELOPER_MODE` option is disabled. Enabling this 13 | option makes tests and other developer targets and options available. Not 14 | enabling this option means that you are a consumer of this project and thus you 15 | have no need for these targets and options. 16 | 17 | Developer mode is always set to on in CI workflows. 18 | 19 | ### Presets 20 | 21 | This project makes use of [presets][1] to simplify the process of configuring 22 | the project. As a developer, you are recommended to always have the [latest 23 | CMake version][2] installed to make use of the latest Quality-of-Life 24 | additions. 25 | 26 | You have a few options to pass `vstat_DEVELOPER_MODE` to the configure 27 | command, but this project prefers to use presets. 28 | 29 | As a developer, you should create a `CMakeUserPresets.json` file at the root of 30 | the project: 31 | 32 | ```json 33 | { 34 | "version": 1, 35 | "cmakeMinimumRequired": { 36 | "major": 3, 37 | "minor": 14, 38 | "patch": 0 39 | }, 40 | "configurePresets": [ 41 | { 42 | "name": "dev", 43 | "binaryDir": "${sourceDir}/build/dev", 44 | "inherits": ["dev-mode", "ci-"] 45 | } 46 | ] 47 | } 48 | ``` 49 | 50 | You should replace `` in your newly created presets file with the name of 51 | the operating system you have, which may be `win64` or `unix`. You can see what 52 | these correspond to in the [`CMakePresets.json`](CMakePresets.json) file. 53 | 54 | `CMakeUserPresets.json` is also the perfect place in which you can put all 55 | sorts of things that you would otherwise want to pass to the configure command 56 | in the terminal. 57 | 58 | ### Configure, build and test 59 | 60 | If you followed the above instructions, then you can configure, build and test 61 | the project respectively with the following commands from the project root on 62 | Windows: 63 | 64 | ```sh 65 | cmake --preset=dev 66 | cmake --build build/dev --config Release 67 | cd build/dev && ctest -C Release 68 | ``` 69 | 70 | And here is the same on a Unix based system (Linux, macOS): 71 | 72 | ```sh 73 | cmake --preset=dev 74 | cmake --build build/dev 75 | cd build/dev && ctest 76 | ``` 77 | 78 | [1]: https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html 79 | [2]: https://cmake.org/download/ 80 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Heal-Research 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

3 | 4 |

5 | 6 | ### Vectorized statistics using SIMD primitives 7 | 8 | [![build-linux](https://github.com/heal-research/vstat/actions/workflows/build-linux.yml/badge.svg)](https://github.com/heal-research/vstat/actions/workflows/build-linux.yml) 9 | [![build-macos](https://github.com/heal-research/vstat/actions/workflows/build-macos.yml/badge.svg)](https://github.com/heal-research/vstat/actions/workflows/build-macos.yml) 10 | 11 | --- 12 | 13 | ### Introduction 14 | 15 | is a C++17 library of computationally efficient methods for calculating sample statistics (mean, variance, covariance, correlation). 16 | 17 | - the implementation builds upon the SIMD abstraction layer provided by the _EVE_ [1] 18 | - it uses a data-parallel _Youngs and Cramer_ [2] algorithm for numerically stable computations of sums and sums-of-squares. 19 | - the results from independent data partitions are combined with the approach by _Schubert and Gertz_ [3]. 20 | - the methods are validated for correctness against statistical methods from the _GNU Scientific Library_ [4]. 21 | 22 | ### Usage 23 | 24 | To use this library you simply need to copy the contents of the `include` folder inside your project, and then `#include `. Defining `VSTAT_NAMESPACE` before inclusion will allow you to set a custom namespace for the library. 25 | 26 | Two convenience methods are provided for batch data: 27 | 28 | - `univariate::accumulate` for univariate statistics (mean, variance, standard deviation) 29 | - `bivariate::accumulate` for bivariate statistics (covariance, correlation) 30 | 31 | The methods return a `statistics` object which contains all the stat values. For example: 32 | 33 | ```cpp 34 | std::vector values{ 1.0, 2.0, 3.0, 4.0 }; 35 | std::vector weights{ 2.0, 4.0, 6.0, 8.0 }; 36 | 37 | // unweighted data 38 | auto stats = univariate::accumulate(values.begin(), values.end()); 39 | std::cout << "stats:\n" << stats << "\n"; 40 | 41 | count: 4 42 | sum: 10 43 | ssr: 5 44 | mean: 2.5 45 | variance: 1.25 46 | sample variance: 1.66667 47 | 48 | // weighted data 49 | auto stats = univariate::accumulate(values.begin(), values.end(), weights.begin()); 50 | std::cout << "stats:\n" << stats << "\n"; 51 | 52 | count: 20 53 | sum: 60 54 | ssr: 20 55 | mean: 3 56 | variance: 1 57 | sample variance: 1.05263 58 | ``` 59 | 60 | Besides iterators, it is also possible to provide raw pointers: 61 | ```cpp 62 | float x[] = { 1., 1., 2., 6. }; 63 | float y[] = { 2., 4., 3., 1. }; 64 | size_t n = std::size(x); 65 | 66 | auto stats = bivariate::accumulate(x, y, n); 67 | std::cout << "stats:\n" << stats << "\n"; 68 | 69 | // results 70 | count: 4 71 | sum_x: 10 72 | ssr_x: 17 73 | mean_x: 2.5 74 | variance_x: 4.25 75 | sample variance_x: 5.66667 76 | sum_y: 10 77 | ssr_y: 5 78 | mean_y: 2.5 79 | variance_y: 1.25 80 | sample variance_y: 1.66667 81 | correlation: -0.759257 82 | covariance: -1.75 83 | sample covariance: -2.33333 84 | ``` 85 | 86 | It is also possible to use _projections_ to aggregate stats over object properties: 87 | ```cpp 88 | struct Foo { 89 | float value; 90 | }; 91 | 92 | Foo foos[] = { {1}, {3}, {5}, {2}, {8} }; 93 | auto stats = univariate::accumulate(foos, std::size(foos), [](auto const& foo) { return foo.value; }); 94 | std::cout << "stats:\n" << stats << "\n"; 95 | 96 | // results 97 | count: 5 98 | sum: 19 99 | ssr: 30.8 100 | mean: 3.8 101 | variance: 6.16 102 | sample variance: 7.7 103 | 104 | struct Foo { 105 | float value; 106 | }; 107 | 108 | struct Bar { 109 | int value; 110 | }; 111 | 112 | Foo foos[] = { {1}, {3}, {5}, {2}, {8} }; 113 | Bar bars[] = { {3}, {2}, {1}, {4}, {11} }; 114 | 115 | auto stats = bivariate::accumulate(foos, bars, std::size(foos), [](auto const& foo) { return foo.value; }, 116 | [](auto const& bar) { return bar.value; }); 117 | std::cout << "stats:\n" << stats << "\n"; 118 | 119 | // results 120 | count: 5 121 | sum_x: 19 122 | ssr_x: 30.8 123 | mean_x: 3.8 124 | variance_x: 6.16 125 | sample variance_x: 7.7 126 | sum_y: 21 127 | ssr_y: 62.8 128 | mean_y: 4.2 129 | variance_y: 12.56 130 | sample variance_y: 15.7 131 | correlation: 0.686676 132 | covariance: 6.04 133 | sample covariance: 7.55 134 | ``` 135 | 136 | The methods above accept a batch of data and calculate relevant statistics. If the data is streaming, then one can also use _accumulators_. The _accumulator_ is a lower-level object that is able to perform calculations online as new data arrives: 137 | ```cpp 138 | univariate_accumulator acc; 139 | acc(1.0); 140 | acc(2.0); 141 | acc(3.0); 142 | acc(4.0); 143 | auto stats = univariate_statistics(acc); 144 | std::cout << "stats:\n" << stats << "\n"; 145 | 146 | Count: 4 147 | Sum: 10 148 | Sum of squares: 5 149 | Mean: 2.5 150 | Variance: 1.25 151 | Sample variance: 1.66667 152 | ``` 153 | The template parameter tells the accumulator how to represent data internally. 154 | 155 | - if a scalar type is provided (e.g. `float` or `double`), the accumulator will perform all operations with scalars (i.e., no SIMD). 156 | - if a SIMD-type is provided (e.g., `eve::wide`) then the accumulator will perform data-parallel operations 157 | 158 | This allows the user to combine accumulators, for example using a SIMD-enabled accumulator to process the bulk of the data and a scalar accumulator for the left-over points. 159 | 160 | #### Available statistics 161 | 162 | - univariate 163 | ```cpp 164 | struct univariate_statistics { 165 | double count; 166 | double sum; 167 | double ssr; 168 | double mean; 169 | double variance; 170 | double sample_variance; 171 | }; 172 | ``` 173 | 174 | - bivariate 175 | ```cpp 176 | struct bivariate_statistics { 177 | double count; 178 | double sum_x; 179 | double sum_y; 180 | double ssr_x; 181 | double ssr_y; 182 | double sum_xy; 183 | double mean_x; 184 | double mean_y; 185 | double variance_x; 186 | double variance_y; 187 | double sample_variance_x; 188 | double sample_variance_y; 189 | double correlation; 190 | double covariance; 191 | double sample_covariance; 192 | }; 193 | ``` 194 | 195 | ### Benchmarks 196 | 197 | The following libraries have been used for performance comparison in the univariate (variance) and bivariate (covariance) case: 198 | 199 | - [linasm statistics](http://linasm.sourceforge.net/docs/api/statistics.php) 1.13 200 | - [boost accumulators](https://www.boost.org/doc/libs/1_69_0/doc/html/accumulators/user_s_guide.html) 1.69 201 | - [gnu scientific library](https://www.gnu.org/software/gsl/) 2.6 202 | - [numpy](https://numpy.org) 1.19.4 203 | 204 | #### Methodology 205 | 206 | - we generate 1M values uniformly distributed between [-1, 1] and save them into a `double` and a `float` array 207 | - increase the data size in 100k increments and benchmark the performance for each method using [nanobench](https://nanobench.ankerl.com/) 208 | 209 | #### Notes 210 | 211 | - we did not use MKL as a backend for numpy and gsl (expect MKL performance to be higher) 212 | - _linasm_ methods for variance and covariance require precomputed array means, so means computation is factored into the benchmarks 213 | - hardware: Ryzen 9 5950X 214 | 215 | ![](./test/benchmarks/var_float.png) 216 | ![](./test/benchmarks/var_double.png) 217 | ![](./test/benchmarks/cov_float.png) 218 | ![](./test/benchmarks/cov_double.png) 219 | 220 | ### Acknowledgements 221 | 222 | [1] [Expressive Vector Engine](https://github.com/jfalcou/eve) 223 | 224 | [2] [Youngs and Cramer - Some Results Relevant to Choice of Sum and Sum-of-Product Algorithms](https://www.jstor.org/stable/1267176?seq=1) 225 | 226 | [3] [Schubert and Gertz - Numerically stable parallel computation of (co-)variance](https://dl.acm.org/doi/10.1145/3221269.3223036) 227 | 228 | [4] [GNU Scientific Library](https://www.gnu.org/software/gsl/) 229 | -------------------------------------------------------------------------------- /cmake/coverage.cmake: -------------------------------------------------------------------------------- 1 | # ---- Variables ---- 2 | 3 | # We use variables separate from what CTest uses, because those have 4 | # customization issues 5 | set( 6 | COVERAGE_TRACE_COMMAND 7 | lcov -c -q 8 | -o "${PROJECT_BINARY_DIR}/coverage.info" 9 | -d "${PROJECT_BINARY_DIR}" 10 | --include "${PROJECT_SOURCE_DIR}/*" 11 | CACHE STRING 12 | "; separated command to generate a trace for the 'coverage' target" 13 | ) 14 | 15 | set( 16 | COVERAGE_HTML_COMMAND 17 | genhtml --legend -f -q 18 | "${PROJECT_BINARY_DIR}/coverage.info" 19 | -p "${PROJECT_SOURCE_DIR}" 20 | -o "${PROJECT_BINARY_DIR}/coverage_html" 21 | CACHE STRING 22 | "; separated command to generate an HTML report for the 'coverage' target" 23 | ) 24 | 25 | # ---- Coverage target ---- 26 | 27 | add_custom_target( 28 | coverage 29 | COMMAND ${COVERAGE_TRACE_COMMAND} 30 | COMMAND ${COVERAGE_HTML_COMMAND} 31 | COMMENT "Generating coverage report" 32 | VERBATIM 33 | ) 34 | -------------------------------------------------------------------------------- /cmake/dev-mode.cmake: -------------------------------------------------------------------------------- 1 | include(CTest) 2 | if(BUILD_TESTING) 3 | add_subdirectory(test) 4 | endif() 5 | 6 | option(VSTAT_BUILD_DOCS "Build documentation using Doxygen" OFF) 7 | if(VSTAT_BUILD_DOCS) 8 | include(cmake/docs.cmake) 9 | endif() 10 | 11 | option(ENABLE_COVERAGE "Enable coverage support separate from CTest's" OFF) 12 | if(ENABLE_COVERAGE) 13 | include(cmake/coverage.cmake) 14 | endif() 15 | 16 | if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") 17 | include(cmake/open-cpp-coverage.cmake OPTIONAL) 18 | endif() 19 | 20 | include(cmake/lint-targets.cmake) 21 | include(cmake/spell-targets.cmake) 22 | -------------------------------------------------------------------------------- /cmake/docs.cmake: -------------------------------------------------------------------------------- 1 | # ---- Redefine docs_early_return ---- 2 | 3 | # This function must be a macro, so the return() takes effect in the calling 4 | # scope. This prevents other targets from being available and potentially 5 | # requiring dependencies. This cuts down on the time it takes to generate 6 | # documentation in CI. 7 | macro(docs_early_return) 8 | return() 9 | endmacro() 10 | 11 | # ---- Dependencies ---- 12 | 13 | find_package(Doxygen) 14 | 15 | if (DOXYGEN_FOUND) 16 | message( STATUS "[vstat] Doxygen available") 17 | set(DOXYGEN_CONFIG ${PROJECT_SOURCE_DIR}/docs/Doxyfile) 18 | set(DOXYGEN_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/docs" 19 | CACHE PATH "Path for the generated Doxygen documentation" 20 | ) 21 | else (DOXYGEN_FOUND) 22 | message( STATUS "[vstat] Doxygen need to be installed to generate the doxygen documentation") 23 | endif (DOXYGEN_FOUND) 24 | 25 | # ---- Declare documentation target ---- 26 | set(working_dir "${PROJECT_BINARY_DIR}/docs") 27 | add_custom_target( 28 | docs 29 | COMMAND "${CMAKE_COMMAND}" -E remove_directory 30 | "${DOXYGEN_OUTPUT_DIRECTORY}/html" 31 | "${DOXYGEN_OUTPUT_DIRECTORY}/xml" 32 | COMMAND DOXYGEN_OUTPUT=${DOXYGEN_OUTPUT_DIRECTORY} ${DOXYGEN_EXECUTABLE} ${DOXYGEN_CONFIG} 33 | COMMENT "Building documentation using Doxygen and doxygen-awesome.css" 34 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/docs 35 | VERBATIM 36 | ) 37 | -------------------------------------------------------------------------------- /cmake/install-config.cmake: -------------------------------------------------------------------------------- 1 | include("${CMAKE_CURRENT_LIST_DIR}/vstatTargets.cmake") 2 | -------------------------------------------------------------------------------- /cmake/install-rules.cmake: -------------------------------------------------------------------------------- 1 | if(PROJECT_IS_TOP_LEVEL) 2 | set(CMAKE_INSTALL_INCLUDEDIR include/vstat CACHE PATH "") 3 | endif() 4 | 5 | # Project is configured with no languages, so tell GNUInstallDirs the lib dir 6 | set(CMAKE_INSTALL_LIBDIR lib CACHE PATH "") 7 | 8 | include(CMakePackageConfigHelpers) 9 | include(GNUInstallDirs) 10 | 11 | # find_package() call for consumers to find this project 12 | set(package vstat) 13 | 14 | install( 15 | DIRECTORY include/ 16 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" 17 | COMPONENT vstat_Development 18 | ) 19 | 20 | install( 21 | TARGETS vstat_vstat 22 | EXPORT vstatTargets 23 | INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" 24 | ) 25 | 26 | if (vstat_BUILD_PYTHON AND Python_FOUND AND nanobind_FOUND) 27 | execute_process( 28 | COMMAND "${Python_EXECUTABLE}" -c "import sysconfig as sc; print(sc.get_path('platlib', 'posix_user', {'userbase': ''})[1:])" 29 | OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE VSTAT_PYTHON_SITELIB) 30 | install( 31 | TARGETS vstat_python 32 | EXPORT vstatTargets 33 | LIBRARY DESTINATION "${VSTAT_PYTHON_SITELIB}/${package}" 34 | ) 35 | endif() 36 | 37 | write_basic_package_version_file( 38 | "${package}ConfigVersion.cmake" 39 | COMPATIBILITY SameMajorVersion 40 | ARCH_INDEPENDENT 41 | ) 42 | 43 | # Allow package maintainers to freely override the path for the configs 44 | set( 45 | vstat_INSTALL_CMAKEDIR "${CMAKE_INSTALL_DATADIR}/${package}" 46 | CACHE PATH "CMake package config location relative to the install prefix" 47 | ) 48 | mark_as_advanced(vstat_INSTALL_CMAKEDIR) 49 | 50 | install( 51 | FILES cmake/install-config.cmake 52 | DESTINATION "${vstat_INSTALL_CMAKEDIR}" 53 | RENAME "${package}Config.cmake" 54 | COMPONENT vstat_Development 55 | ) 56 | 57 | install( 58 | FILES "${PROJECT_BINARY_DIR}/${package}ConfigVersion.cmake" 59 | DESTINATION "${vstat_INSTALL_CMAKEDIR}" 60 | COMPONENT vstat_Development 61 | ) 62 | 63 | install( 64 | EXPORT vstatTargets 65 | NAMESPACE vstat:: 66 | DESTINATION "${vstat_INSTALL_CMAKEDIR}" 67 | COMPONENT vstat_Development 68 | ) 69 | 70 | if(PROJECT_IS_TOP_LEVEL) 71 | include(CPack) 72 | endif() 73 | 74 | -------------------------------------------------------------------------------- /cmake/lint-targets.cmake: -------------------------------------------------------------------------------- 1 | set( 2 | FORMAT_PATTERNS 3 | source/*.cpp source/*.hpp 4 | include/*.hpp 5 | test/*.cpp test/*.hpp 6 | CACHE STRING 7 | "; separated patterns relative to the project source dir to format" 8 | ) 9 | 10 | set(FORMAT_COMMAND clang-format CACHE STRING "Formatter to use") 11 | 12 | add_custom_target( 13 | format-check 14 | COMMAND "${CMAKE_COMMAND}" 15 | -D "FORMAT_COMMAND=${FORMAT_COMMAND}" 16 | -D "PATTERNS=${FORMAT_PATTERNS}" 17 | -P "${PROJECT_SOURCE_DIR}/cmake/lint.cmake" 18 | WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" 19 | COMMENT "Linting the code" 20 | VERBATIM 21 | ) 22 | 23 | add_custom_target( 24 | format-fix 25 | COMMAND "${CMAKE_COMMAND}" 26 | -D "FORMAT_COMMAND=${FORMAT_COMMAND}" 27 | -D "PATTERNS=${FORMAT_PATTERNS}" 28 | -D FIX=YES 29 | -P "${PROJECT_SOURCE_DIR}/cmake/lint.cmake" 30 | WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" 31 | COMMENT "Fixing the code" 32 | VERBATIM 33 | ) 34 | -------------------------------------------------------------------------------- /cmake/lint.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | macro(default name) 4 | if(NOT DEFINED "${name}") 5 | set("${name}" "${ARGN}") 6 | endif() 7 | endmacro() 8 | 9 | default(FORMAT_COMMAND clang-format) 10 | default( 11 | PATTERNS 12 | source/*.cpp source/*.hpp 13 | include/*.hpp 14 | test/*.cpp test/*.hpp 15 | ) 16 | default(FIX NO) 17 | 18 | set(flag --output-replacements-xml) 19 | set(args OUTPUT_VARIABLE output) 20 | if(FIX) 21 | set(flag -i) 22 | set(args "") 23 | endif() 24 | 25 | file(GLOB_RECURSE files ${PATTERNS}) 26 | set(badly_formatted "") 27 | set(output "") 28 | string(LENGTH "${CMAKE_SOURCE_DIR}/" path_prefix_length) 29 | 30 | foreach(file IN LISTS files) 31 | execute_process( 32 | COMMAND "${FORMAT_COMMAND}" --style=file "${flag}" "${file}" 33 | WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" 34 | RESULT_VARIABLE result 35 | ${args} 36 | ) 37 | if(NOT result EQUAL "0") 38 | message(FATAL_ERROR "'${file}': formatter returned with ${result}") 39 | endif() 40 | if(NOT FIX AND output MATCHES "\n ...) 5 | function(windows_set_path TEST) 6 | if(NOT CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") 7 | return() 8 | endif() 9 | 10 | set(path "") 11 | set(glue "") 12 | foreach(target IN LISTS ARGN) 13 | get_target_property(type "${target}" TYPE) 14 | if(type STREQUAL "SHARED_LIBRARY") 15 | set(path "${path}${glue}$") 16 | set(glue "\;") # backslash is important 17 | endif() 18 | endforeach() 19 | if(NOT path STREQUAL "") 20 | set_property(TEST "${TEST}" PROPERTY ENVIRONMENT "PATH=${path}") 21 | endif() 22 | endfunction() 23 | -------------------------------------------------------------------------------- /docs/Doxyfile: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------------------------------- 2 | # Project related configuration options 3 | #--------------------------------------------------------------------------- 4 | DOXYFILE_ENCODING = UTF-8 5 | 6 | PROJECT_NAME = vstat 7 | PROJECT_NUMBER = 8 | PROJECT_BRIEF = 9 | PROJECT_LOGO = ../logo/vstat.svg 10 | 11 | GENERATE_HTML = YES 12 | USE_MATHJAX = YES 13 | MATHJAX_FORMAT = HTML-CSS 14 | GENERATE_TREEVIEW = YES # optional. Also works without treeview 15 | DISABLE_INDEX = NO 16 | FULL_SIDEBAR = NO 17 | HTML_EXTRA_FILES = doxygen-awesome-darkmode-toggle.js 18 | HTML_EXTRA_STYLESHEET = doxygen-awesome.css \ 19 | doxygen-awesome-sidebar-only.css \ 20 | doxygen-awesome-sidebar-only-darkmode-toggle.css 21 | HTML_COLORSTYLE = LIGHT # required with Doxygen >= 1.9.5 22 | HTML_HEADER = header.html 23 | HTML_FOOTER = 24 | USE_MDFILE_AS_MAINPAGE = ../README.md 25 | LAYOUT_FILE = layout.xml 26 | 27 | GENERATE_LATEX = NO 28 | GENERATE_RTF = NO 29 | GENERATE_MAN = NO 30 | GENERATE_XML = NO 31 | GENERATE_DOCBOOK = NO 32 | GENERATE_AUTOGEN_DEF = NO 33 | GENERATE_PERLMOD = NO 34 | 35 | EXTENSION_MAPPING = 36 | MARKDOWN_SUPPORT = YES 37 | TOC_INCLUDE_HEADINGS = 5 38 | AUTOLINK_SUPPORT = YES 39 | LOOKUP_CACHE_SIZE = 0 40 | 41 | 42 | HIDE_UNDOC_MEMBERS = YES 43 | HIDE_UNDOC_CLASSES = YES 44 | INPUT = ../include/vstat ../README.md index.md . 45 | INPUT_ENCODING = UTF-8 46 | INCLUDE_PATH = 47 | FILE_PATTERNS = *.hpp \ 48 | *.cpp 49 | RECURSIVE = YES 50 | EXTRACT_ALL = NO 51 | HIDE_UNDOC_CLASSES = YES 52 | HIDE_UNDOC_MEMBERS = YES 53 | BRIEF_MEMBER_DESC = NO 54 | SHOW_NAMESPACES = NO 55 | HIDE_SCOPE_NAMES = YES 56 | -------------------------------------------------------------------------------- /docs/doxygen-awesome-darkmode-toggle.js: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | Doxygen Awesome 4 | https://github.com/jothepro/doxygen-awesome-css 5 | 6 | MIT License 7 | 8 | Copyright (c) 2021 - 2023 jothepro 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | */ 29 | 30 | class DoxygenAwesomeDarkModeToggle extends HTMLElement { 31 | // SVG icons from https://fonts.google.com/icons 32 | // Licensed under the Apache 2.0 license: 33 | // https://www.apache.org/licenses/LICENSE-2.0.html 34 | static lightModeIcon = `` 35 | static darkModeIcon = `` 36 | static title = "Toggle Light/Dark Mode" 37 | 38 | static prefersLightModeInDarkModeKey = "prefers-light-mode-in-dark-mode" 39 | static prefersDarkModeInLightModeKey = "prefers-dark-mode-in-light-mode" 40 | 41 | static _staticConstructor = function() { 42 | DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.userPreference) 43 | // Update the color scheme when the browsers preference changes 44 | // without user interaction on the website. 45 | window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => { 46 | DoxygenAwesomeDarkModeToggle.onSystemPreferenceChanged() 47 | }) 48 | // Update the color scheme when the tab is made visible again. 49 | // It is possible that the appearance was changed in another tab 50 | // while this tab was in the background. 51 | document.addEventListener("visibilitychange", visibilityState => { 52 | if (document.visibilityState === 'visible') { 53 | DoxygenAwesomeDarkModeToggle.onSystemPreferenceChanged() 54 | } 55 | }); 56 | }() 57 | 58 | static init() { 59 | $(function() { 60 | $(document).ready(function() { 61 | const toggleButton = document.createElement('doxygen-awesome-dark-mode-toggle') 62 | toggleButton.title = DoxygenAwesomeDarkModeToggle.title 63 | toggleButton.updateIcon() 64 | 65 | window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => { 66 | toggleButton.updateIcon() 67 | }) 68 | document.addEventListener("visibilitychange", visibilityState => { 69 | if (document.visibilityState === 'visible') { 70 | toggleButton.updateIcon() 71 | } 72 | }); 73 | 74 | $(document).ready(function(){ 75 | document.getElementById("MSearchBox").parentNode.appendChild(toggleButton) 76 | }) 77 | $(window).resize(function(){ 78 | document.getElementById("MSearchBox").parentNode.appendChild(toggleButton) 79 | }) 80 | }) 81 | }) 82 | } 83 | 84 | constructor() { 85 | super(); 86 | this.onclick=this.toggleDarkMode 87 | } 88 | 89 | /** 90 | * @returns `true` for dark-mode, `false` for light-mode system preference 91 | */ 92 | static get systemPreference() { 93 | return window.matchMedia('(prefers-color-scheme: dark)').matches 94 | } 95 | 96 | /** 97 | * @returns `true` for dark-mode, `false` for light-mode user preference 98 | */ 99 | static get userPreference() { 100 | return (!DoxygenAwesomeDarkModeToggle.systemPreference && localStorage.getItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey)) || 101 | (DoxygenAwesomeDarkModeToggle.systemPreference && !localStorage.getItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey)) 102 | } 103 | 104 | static set userPreference(userPreference) { 105 | DoxygenAwesomeDarkModeToggle.darkModeEnabled = userPreference 106 | if(!userPreference) { 107 | if(DoxygenAwesomeDarkModeToggle.systemPreference) { 108 | localStorage.setItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey, true) 109 | } else { 110 | localStorage.removeItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey) 111 | } 112 | } else { 113 | if(!DoxygenAwesomeDarkModeToggle.systemPreference) { 114 | localStorage.setItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey, true) 115 | } else { 116 | localStorage.removeItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey) 117 | } 118 | } 119 | DoxygenAwesomeDarkModeToggle.onUserPreferenceChanged() 120 | } 121 | 122 | static enableDarkMode(enable) { 123 | if(enable) { 124 | DoxygenAwesomeDarkModeToggle.darkModeEnabled = true 125 | document.documentElement.classList.add("dark-mode") 126 | document.documentElement.classList.remove("light-mode") 127 | } else { 128 | DoxygenAwesomeDarkModeToggle.darkModeEnabled = false 129 | document.documentElement.classList.remove("dark-mode") 130 | document.documentElement.classList.add("light-mode") 131 | } 132 | } 133 | 134 | static onSystemPreferenceChanged() { 135 | DoxygenAwesomeDarkModeToggle.darkModeEnabled = DoxygenAwesomeDarkModeToggle.userPreference 136 | DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.darkModeEnabled) 137 | } 138 | 139 | static onUserPreferenceChanged() { 140 | DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.darkModeEnabled) 141 | } 142 | 143 | toggleDarkMode() { 144 | DoxygenAwesomeDarkModeToggle.userPreference = !DoxygenAwesomeDarkModeToggle.userPreference 145 | this.updateIcon() 146 | } 147 | 148 | updateIcon() { 149 | if(DoxygenAwesomeDarkModeToggle.darkModeEnabled) { 150 | this.innerHTML = DoxygenAwesomeDarkModeToggle.darkModeIcon 151 | } else { 152 | this.innerHTML = DoxygenAwesomeDarkModeToggle.lightModeIcon 153 | } 154 | } 155 | } 156 | 157 | customElements.define("doxygen-awesome-dark-mode-toggle", DoxygenAwesomeDarkModeToggle); 158 | -------------------------------------------------------------------------------- /docs/doxygen-awesome-fragment-copy-button.js: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | Doxygen Awesome 4 | https://github.com/jothepro/doxygen-awesome-css 5 | 6 | MIT License 7 | 8 | Copyright (c) 2022 - 2023 jothepro 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | */ 29 | 30 | class DoxygenAwesomeFragmentCopyButton extends HTMLElement { 31 | constructor() { 32 | super(); 33 | this.onclick=this.copyContent 34 | } 35 | static title = "Copy to clipboard" 36 | static copyIcon = `` 37 | static successIcon = `` 38 | static successDuration = 980 39 | static init() { 40 | $(function() { 41 | $(document).ready(function() { 42 | if(navigator.clipboard) { 43 | const fragments = document.getElementsByClassName("fragment") 44 | for(const fragment of fragments) { 45 | const fragmentWrapper = document.createElement("div") 46 | fragmentWrapper.className = "doxygen-awesome-fragment-wrapper" 47 | const fragmentCopyButton = document.createElement("doxygen-awesome-fragment-copy-button") 48 | fragmentCopyButton.innerHTML = DoxygenAwesomeFragmentCopyButton.copyIcon 49 | fragmentCopyButton.title = DoxygenAwesomeFragmentCopyButton.title 50 | 51 | fragment.parentNode.replaceChild(fragmentWrapper, fragment) 52 | fragmentWrapper.appendChild(fragment) 53 | fragmentWrapper.appendChild(fragmentCopyButton) 54 | 55 | } 56 | } 57 | }) 58 | }) 59 | } 60 | 61 | 62 | copyContent() { 63 | const content = this.previousSibling.cloneNode(true) 64 | // filter out line number from file listings 65 | content.querySelectorAll(".lineno, .ttc").forEach((node) => { 66 | node.remove() 67 | }) 68 | let textContent = content.textContent 69 | // remove trailing newlines that appear in file listings 70 | let numberOfTrailingNewlines = 0 71 | while(textContent.charAt(textContent.length - (numberOfTrailingNewlines + 1)) == '\n') { 72 | numberOfTrailingNewlines++; 73 | } 74 | textContent = textContent.substring(0, textContent.length - numberOfTrailingNewlines) 75 | navigator.clipboard.writeText(textContent); 76 | this.classList.add("success") 77 | this.innerHTML = DoxygenAwesomeFragmentCopyButton.successIcon 78 | window.setTimeout(() => { 79 | this.classList.remove("success") 80 | this.innerHTML = DoxygenAwesomeFragmentCopyButton.copyIcon 81 | }, DoxygenAwesomeFragmentCopyButton.successDuration); 82 | } 83 | } 84 | 85 | customElements.define("doxygen-awesome-fragment-copy-button", DoxygenAwesomeFragmentCopyButton) 86 | -------------------------------------------------------------------------------- /docs/doxygen-awesome-interactive-toc.js: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | Doxygen Awesome 4 | https://github.com/jothepro/doxygen-awesome-css 5 | 6 | MIT License 7 | 8 | Copyright (c) 2022 - 2023 jothepro 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | */ 29 | 30 | class DoxygenAwesomeInteractiveToc { 31 | static topOffset = 38 32 | static hideMobileMenu = true 33 | static headers = [] 34 | 35 | static init() { 36 | window.addEventListener("load", () => { 37 | let toc = document.querySelector(".contents > .toc") 38 | if(toc) { 39 | toc.classList.add("interactive") 40 | if(!DoxygenAwesomeInteractiveToc.hideMobileMenu) { 41 | toc.classList.add("open") 42 | } 43 | document.querySelector(".contents > .toc > h3")?.addEventListener("click", () => { 44 | if(toc.classList.contains("open")) { 45 | toc.classList.remove("open") 46 | } else { 47 | toc.classList.add("open") 48 | } 49 | }) 50 | 51 | document.querySelectorAll(".contents > .toc > ul a").forEach((node) => { 52 | let id = node.getAttribute("href").substring(1) 53 | DoxygenAwesomeInteractiveToc.headers.push({ 54 | node: node, 55 | headerNode: document.getElementById(id) 56 | }) 57 | 58 | document.getElementById("doc-content")?.addEventListener("scroll", () => { 59 | DoxygenAwesomeInteractiveToc.update() 60 | }) 61 | }) 62 | DoxygenAwesomeInteractiveToc.update() 63 | } 64 | }) 65 | } 66 | 67 | static update() { 68 | let active = DoxygenAwesomeInteractiveToc.headers[0]?.node 69 | DoxygenAwesomeInteractiveToc.headers.forEach((header) => { 70 | let position = header.headerNode.getBoundingClientRect().top 71 | header.node.classList.remove("active") 72 | header.node.classList.remove("aboveActive") 73 | if(position < DoxygenAwesomeInteractiveToc.topOffset) { 74 | active = header.node 75 | active?.classList.add("aboveActive") 76 | } 77 | }) 78 | active?.classList.add("active") 79 | active?.classList.remove("aboveActive") 80 | } 81 | } -------------------------------------------------------------------------------- /docs/doxygen-awesome-paragraph-link.js: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | Doxygen Awesome 4 | https://github.com/jothepro/doxygen-awesome-css 5 | 6 | MIT License 7 | 8 | Copyright (c) 2022 - 2023 jothepro 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | */ 29 | 30 | class DoxygenAwesomeParagraphLink { 31 | // Icon from https://fonts.google.com/icons 32 | // Licensed under the Apache 2.0 license: 33 | // https://www.apache.org/licenses/LICENSE-2.0.html 34 | static icon = `` 35 | static title = "Permanent Link" 36 | static init() { 37 | $(function() { 38 | $(document).ready(function() { 39 | document.querySelectorAll(".contents a.anchor[id], .contents .groupheader > a[id]").forEach((node) => { 40 | let anchorlink = document.createElement("a") 41 | anchorlink.setAttribute("href", `#${node.getAttribute("id")}`) 42 | anchorlink.setAttribute("title", DoxygenAwesomeParagraphLink.title) 43 | anchorlink.classList.add("anchorlink") 44 | node.classList.add("anchor") 45 | anchorlink.innerHTML = DoxygenAwesomeParagraphLink.icon 46 | node.parentElement.appendChild(anchorlink) 47 | }) 48 | }) 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /docs/doxygen-awesome-sidebar-only-darkmode-toggle.css: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | 4 | Doxygen Awesome 5 | https://github.com/jothepro/doxygen-awesome-css 6 | 7 | MIT License 8 | 9 | Copyright (c) 2021 - 2023 jothepro 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | 29 | */ 30 | 31 | @media screen and (min-width: 768px) { 32 | 33 | #MSearchBox { 34 | width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - var(--searchbar-height) - 1px); 35 | } 36 | 37 | #MSearchField { 38 | width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 66px - var(--searchbar-height)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /docs/doxygen-awesome-sidebar-only.css: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | Doxygen Awesome 4 | https://github.com/jothepro/doxygen-awesome-css 5 | 6 | MIT License 7 | 8 | Copyright (c) 2021 - 2023 jothepro 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | */ 29 | 30 | html { 31 | /* side nav width. MUST be = `TREEVIEW_WIDTH`. 32 | * Make sure it is wide enough to contain the page title (logo + title + version) 33 | */ 34 | --side-nav-fixed-width: 335px; 35 | --menu-display: none; 36 | 37 | --top-height: 120px; 38 | --toc-sticky-top: -25px; 39 | --toc-max-height: calc(100vh - 2 * var(--spacing-medium) - 25px); 40 | } 41 | 42 | #projectname { 43 | white-space: nowrap; 44 | } 45 | 46 | 47 | @media screen and (min-width: 768px) { 48 | html { 49 | --searchbar-background: var(--page-background-color); 50 | } 51 | 52 | #side-nav { 53 | min-width: var(--side-nav-fixed-width); 54 | max-width: var(--side-nav-fixed-width); 55 | top: var(--top-height); 56 | overflow: visible; 57 | } 58 | 59 | #nav-tree, #side-nav { 60 | height: calc(100vh - var(--top-height)) !important; 61 | } 62 | 63 | #nav-tree { 64 | padding: 0; 65 | } 66 | 67 | #top { 68 | display: block; 69 | border-bottom: none; 70 | height: var(--top-height); 71 | margin-bottom: calc(0px - var(--top-height)); 72 | max-width: var(--side-nav-fixed-width); 73 | overflow: hidden; 74 | background: var(--side-nav-background); 75 | } 76 | #main-nav { 77 | float: left; 78 | padding-right: 0; 79 | } 80 | 81 | .ui-resizable-handle { 82 | cursor: default; 83 | width: 1px !important; 84 | background: var(--separator-color); 85 | box-shadow: 0 calc(-2 * var(--top-height)) 0 0 var(--separator-color); 86 | } 87 | 88 | #nav-path { 89 | position: fixed; 90 | right: 0; 91 | left: var(--side-nav-fixed-width); 92 | bottom: 0; 93 | width: auto; 94 | } 95 | 96 | #doc-content { 97 | height: calc(100vh - 31px) !important; 98 | padding-bottom: calc(3 * var(--spacing-large)); 99 | padding-top: calc(var(--top-height) - 80px); 100 | box-sizing: border-box; 101 | margin-left: var(--side-nav-fixed-width) !important; 102 | } 103 | 104 | #MSearchBox { 105 | width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium))); 106 | } 107 | 108 | #MSearchField { 109 | width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 65px); 110 | } 111 | 112 | #MSearchResultsWindow { 113 | left: var(--spacing-medium) !important; 114 | right: auto; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /docs/doxygen-awesome-tabs.js: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | Doxygen Awesome 4 | https://github.com/jothepro/doxygen-awesome-css 5 | 6 | MIT License 7 | 8 | Copyright (c) 2023 jothepro 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | */ 29 | 30 | class DoxygenAwesomeTabs { 31 | 32 | static init() { 33 | window.addEventListener("load", () => { 34 | document.querySelectorAll(".tabbed:not(:empty)").forEach((tabbed, tabbedIndex) => { 35 | let tabLinkList = [] 36 | tabbed.querySelectorAll(":scope > ul > li").forEach((tab, tabIndex) => { 37 | tab.id = "tab_" + tabbedIndex + "_" + tabIndex 38 | let header = tab.querySelector(".tab-title") 39 | let tabLink = document.createElement("button") 40 | tabLink.classList.add("tab-button") 41 | tabLink.appendChild(header) 42 | header.title = header.textContent 43 | tabLink.addEventListener("click", () => { 44 | tabbed.querySelectorAll(":scope > ul > li").forEach((tab) => { 45 | tab.classList.remove("selected") 46 | }) 47 | tabLinkList.forEach((tabLink) => { 48 | tabLink.classList.remove("active") 49 | }) 50 | tab.classList.add("selected") 51 | tabLink.classList.add("active") 52 | }) 53 | tabLinkList.push(tabLink) 54 | if(tabIndex == 0) { 55 | tab.classList.add("selected") 56 | tabLink.classList.add("active") 57 | } 58 | }) 59 | let tabsOverview = document.createElement("div") 60 | tabsOverview.classList.add("tabs-overview") 61 | let tabsOverviewContainer = document.createElement("div") 62 | tabsOverviewContainer.classList.add("tabs-overview-container") 63 | tabLinkList.forEach((tabLink) => { 64 | tabsOverview.appendChild(tabLink) 65 | }) 66 | tabsOverviewContainer.appendChild(tabsOverview) 67 | tabbed.before(tabsOverviewContainer) 68 | 69 | function resize() { 70 | let maxTabHeight = 0 71 | tabbed.querySelectorAll(":scope > ul > li").forEach((tab, tabIndex) => { 72 | let visibility = tab.style.display 73 | tab.style.display = "block" 74 | maxTabHeight = Math.max(tab.offsetHeight, maxTabHeight) 75 | tab.style.display = visibility 76 | }) 77 | tabbed.style.height = `${maxTabHeight + 10}px` 78 | } 79 | 80 | resize() 81 | new ResizeObserver(resize).observe(tabbed) 82 | }) 83 | }) 84 | 85 | } 86 | 87 | static resize(tabbed) { 88 | 89 | } 90 | } -------------------------------------------------------------------------------- /docs/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | $projectname: $title 10 | $title 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 32 | $treeview 33 | $search 34 | $mathjax 35 | $darkmode 36 | 37 | $extrastylesheet 38 | 39 | 40 | 41 | 42 | 43 |
44 | 45 | 46 | 47 |
48 | 49 | 50 |
51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 63 | 64 | 65 | 66 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |
59 |
$projectname $projectnumber 60 |
61 |
$projectbrief
62 |
67 |
$projectbrief
68 |
$searchbox
$searchbox
86 |
87 | 88 | 89 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | _vstat_ is a C++20 library of computationally efficient methods for calculating sample statistics (mean, variance, covariance, correlation), common regression metrics (\f$R^2\f$ score, MSE, MAE) and likelihoods (_Gaussian_ and _Poisson_). 4 | 5 | The library has the following features: 6 | 7 | - the implementation builds upon the SIMD abstraction layer provided by [E.V.E](https://jfalcou.github.io/eve/) (The Expressive Vector Engine) 8 | - it uses a data-parallel version of the numerically-stable algorithm from [Edward A. Youngs and Elliot M. Cramer](https://www.jstor.org/stable/1267176?seq=1) where the results from independent data partitions are combined with the approach by [_Schubert and Gertz_](https://dl.acm.org/doi/10.1145/3221269.3223036) 9 | 10 | ## Methodology 11 | 12 | 13 | 14 | ## Bibliography 15 | 16 | Edward A. Youngs and Elliot M. Cramer, _Some Results Relevant to Choice of Sum and Sum-of-Product Algorithms_, Technometrics Vol. 13, No. 3 (Aug., 1971), pp. 657-665 (9 pages)\n 17 | https://doi.org/10.2307/1267176 18 | 19 | Erich Schubert and Michael Gertz, _Numerically stable parallel computation of (co-)variance_, SSDBM '18, Article No. 10, Pages 1–12\n 20 | https://doi.org/10.1145/3221269.3223036 -------------------------------------------------------------------------------- /docs/layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-parts": { 4 | "inputs": { 5 | "nixpkgs-lib": "nixpkgs-lib" 6 | }, 7 | "locked": { 8 | "lastModified": 1743550720, 9 | "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=", 10 | "owner": "hercules-ci", 11 | "repo": "flake-parts", 12 | "rev": "c621e8422220273271f52058f618c94e405bb0f5", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "hercules-ci", 17 | "repo": "flake-parts", 18 | "type": "github" 19 | } 20 | }, 21 | "foolnotion": { 22 | "inputs": { 23 | "nixpkgs": [ 24 | "nixpkgs" 25 | ] 26 | }, 27 | "locked": { 28 | "lastModified": 1743785062, 29 | "narHash": "sha256-9AkEHvyCpx2MQ6o/53kWDhGdFJxVa1MGVTeZY9Q2AdM=", 30 | "owner": "foolnotion", 31 | "repo": "nur-pkg", 32 | "rev": "6edd3a0f5114e712ad7d8893a10f61c6359e61c1", 33 | "type": "github" 34 | }, 35 | "original": { 36 | "owner": "foolnotion", 37 | "repo": "nur-pkg", 38 | "type": "github" 39 | } 40 | }, 41 | "nixpkgs": { 42 | "locked": { 43 | "lastModified": 1743936397, 44 | "narHash": "sha256-2vbzf8aWg7m6BANYF2O5vVj0zC9A0cvood86isMWQC4=", 45 | "owner": "nixos", 46 | "repo": "nixpkgs", 47 | "rev": "2961e003140d577e8b63c0055846af82601aa9a4", 48 | "type": "github" 49 | }, 50 | "original": { 51 | "owner": "nixos", 52 | "ref": "master", 53 | "repo": "nixpkgs", 54 | "type": "github" 55 | } 56 | }, 57 | "nixpkgs-lib": { 58 | "locked": { 59 | "lastModified": 1743296961, 60 | "narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=", 61 | "owner": "nix-community", 62 | "repo": "nixpkgs.lib", 63 | "rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa", 64 | "type": "github" 65 | }, 66 | "original": { 67 | "owner": "nix-community", 68 | "repo": "nixpkgs.lib", 69 | "type": "github" 70 | } 71 | }, 72 | "root": { 73 | "inputs": { 74 | "flake-parts": "flake-parts", 75 | "foolnotion": "foolnotion", 76 | "nixpkgs": "nixpkgs" 77 | } 78 | } 79 | }, 80 | "root": "root", 81 | "version": 7 82 | } 83 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "vstat dev"; 3 | 4 | inputs = { 5 | flake-parts.url = "github:hercules-ci/flake-parts"; 6 | foolnotion.url = "github:foolnotion/nur-pkg"; 7 | nixpkgs.url = "github:nixos/nixpkgs/master"; 8 | foolnotion.inputs.nixpkgs.follows = "nixpkgs"; 9 | }; 10 | 11 | outputs = inputs@{ self, flake-parts, nixpkgs, foolnotion }: 12 | flake-parts.lib.mkFlake { inherit inputs; } { 13 | systems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ]; 14 | 15 | perSystem = { pkgs, system, ... }: 16 | let 17 | pkgs = import nixpkgs { 18 | inherit system; 19 | overlays = [ foolnotion.overlay ]; 20 | }; 21 | stdenv = pkgs.llvmPackages_latest.stdenv; 22 | pythonPkgs = pkgs.python3Packages.override { 23 | overrides = self: super: { 24 | nanobind = super.nanobind.overridePythonAttrs (old: { 25 | doCheck = false; 26 | build-system = old.build-system ++ [ pythonPkgs.typing-extensions ]; 27 | }); 28 | }; 29 | }; 30 | nanobind = pythonPkgs.nanobind; 31 | in 32 | rec { 33 | devShells.default = stdenv.mkDerivation { 34 | name = "vstat-dev"; 35 | hardeningDisable = [ "all" ]; 36 | impureUseNativeOptimizations = true; 37 | nativeBuildInputs = with pkgs; [ 38 | cmake 39 | clang-tools 40 | cppcheck 41 | gdb 42 | doxygen 43 | valgrind 44 | nanobind 45 | ]; 46 | buildInputs = packages.default.buildInputs 47 | ++ (with pkgs; [ boost doctest gsl pkg-config ]) 48 | ++ (with pkgs; if pkgs.stdenv.isx86_64 then [ linasm ] else []); 49 | }; 50 | 51 | packages.vstat = stdenv.mkDerivation { 52 | name = "vstat"; 53 | src = self; 54 | nativeBuildInputs = with pkgs; [ cmake ]; 55 | buildInputs = with pkgs; [ eve ]; 56 | 57 | cmakeFlags = [ 58 | "-DCMAKE_CXX_FLAGS=${ 59 | if pkgs.stdenv.hostPlatform.isx86_64 then "-march=x86-64" else "" 60 | }" 61 | "-DCPM_USE_LOCAL_PACKAGES=1" 62 | ]; 63 | }; 64 | 65 | packages.vstat-python = stdenv.mkDerivation { 66 | name = "vstat"; 67 | src = self; 68 | nativeBuildInputs = with pkgs; [ cmake ]; 69 | buildInputs = packages.vstat.buildInputs ++ [ nanobind ]; 70 | cmakeFlags = packages.vstat.cmakeFlags ++ [ "-Dvstat_BUILD_PYTHON=ON" ]; 71 | }; 72 | 73 | packages.default = packages.vstat; 74 | }; 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /include/vstat/bivariate.hpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // SPDX-FileCopyrightText: Copyright 2020-2024 Heal Research 3 | 4 | #ifndef VSTAT_BIVARIATE_HPP 5 | #define VSTAT_BIVARIATE_HPP 6 | 7 | #include "combine.hpp" 8 | 9 | namespace VSTAT_NAMESPACE { 10 | /*! 11 | \brief Bivariate accumulator object 12 | */ 13 | template 14 | struct bivariate_accumulator { 15 | static auto load_state(T sx, T sy, T sw, T sxx, T syy, T sxy) noexcept -> bivariate_accumulator // NOLINT 16 | { 17 | bivariate_accumulator acc; 18 | acc.sum_w = sw; 19 | acc.sum_w_old = sw; 20 | acc.sum_x = sx; 21 | acc.sum_y = sy; 22 | acc.sum_xx = sxx; 23 | acc.sum_yy = syy; 24 | acc.sum_xy = sxy; 25 | return acc; 26 | } 27 | 28 | static auto load_state(std::tuple state) noexcept -> bivariate_accumulator 29 | { 30 | auto [sx, sy, sw, sxx, syy, sxy] = state; 31 | return load_state(sx, sy, sw, sxx, syy, sxy); 32 | } 33 | 34 | inline void operator()(T x, T y) noexcept 35 | { 36 | T dx = x * sum_w - sum_x; 37 | T dy = y * sum_w - sum_y; 38 | 39 | sum_w += 1; 40 | 41 | T f = 1. / (sum_w * sum_w_old); 42 | sum_xx += f * dx * dx; 43 | sum_yy += f * dy * dy; 44 | sum_xy += f * dx * dy; 45 | 46 | sum_x += x; 47 | sum_y += y; 48 | 49 | sum_w_old = sum_w; 50 | } 51 | 52 | inline void operator()(T x, T y, T w) noexcept // NOLINT 53 | { 54 | T dx = x * sum_w - sum_x; 55 | T dy = y * sum_w - sum_y; 56 | 57 | sum_x += x * w; 58 | sum_y += y * w; 59 | sum_w += w; 60 | 61 | T f = w / (sum_w * sum_w_old); 62 | sum_xx += f * dx * dx; 63 | sum_yy += f * dy * dy; 64 | sum_xy += f * dx * dy; 65 | 66 | sum_w_old = sum_w; 67 | } 68 | 69 | template 70 | requires eve::simd_value && eve::simd_compatible_ptr 71 | inline void operator()(U const* x, U const* y) noexcept 72 | { 73 | (*this)(T{x}, T{y}); 74 | } 75 | 76 | template 77 | requires eve::simd_value && eve::simd_compatible_ptr 78 | inline void operator()(U const* x, U const* y, U const* w) noexcept 79 | { 80 | (*this)(T{x}, T{y}, T{w}); 81 | } 82 | 83 | // performs a reduction on the vector types and returns the sums and the squared residuals sums 84 | auto stats() const noexcept -> std::tuple 85 | { 86 | if constexpr (std::is_floating_point_v) { 87 | return { sum_w, sum_x, sum_y, sum_xx, sum_yy, sum_xy }; 88 | } else { 89 | auto [sxx, syy, sxy] = combine(sum_w, sum_x, sum_y, sum_xx, sum_yy, sum_xy); 90 | return { eve::reduce(sum_w), eve::reduce(sum_x), eve::reduce(sum_y), sxx, syy, sxy }; 91 | } 92 | } 93 | 94 | private: 95 | // sum of weights 96 | T sum_w{0}; 97 | T sum_w_old{1}; 98 | // means 99 | T sum_x{0}; 100 | T sum_y{0}; 101 | // squared residuals 102 | T sum_xx{0}; 103 | T sum_yy{0}; 104 | T sum_xy{0}; 105 | }; 106 | 107 | /*! 108 | \brief Bivariate statistics 109 | */ 110 | struct bivariate_statistics { 111 | double count; 112 | double sum_x; 113 | double sum_y; 114 | double ssr_x; 115 | double ssr_y; 116 | double sum_xy; 117 | double mean_x; 118 | double mean_y; 119 | double variance_x; 120 | double variance_y; 121 | double sample_variance_x; 122 | double sample_variance_y; 123 | double correlation; 124 | double covariance; 125 | double sample_covariance; 126 | 127 | template 128 | explicit bivariate_statistics(T accumulator) 129 | { 130 | auto [sw, sx, sy, sxx, syy, sxy] = accumulator.stats(); 131 | count = sw; 132 | sum_x = sx; 133 | sum_y = sy; 134 | ssr_x = sxx; 135 | ssr_y = syy; 136 | sum_xy = sxy; 137 | mean_x = sx / sw; 138 | mean_y = sy / sw; 139 | variance_x = sxx / sw; 140 | variance_y = syy / sw; 141 | sample_variance_x = sxx / (sw - 1); 142 | sample_variance_y = syy / (sw - 1); 143 | 144 | if (!(sxx > 0 && syy > 0)) { 145 | correlation = static_cast(sxx == syy); 146 | } else { 147 | correlation = sxy / std::sqrt(sxx * syy); 148 | } 149 | 150 | covariance = sxy / sw; 151 | sample_covariance = sxy / (sw - 1); 152 | } 153 | }; 154 | 155 | inline auto operator<<(std::ostream& os, bivariate_statistics const& stats) -> std::ostream& 156 | { 157 | os << "count: \t" << stats.count 158 | << "\nsum_x: \t" << stats.sum_x 159 | << "\nssr_x: \t" << stats.ssr_x 160 | << "\nmean_x: \t" << stats.mean_x 161 | << "\nvariance_x: \t" << stats.variance_x 162 | << "\nsample variance_x:\t" << stats.sample_variance_x 163 | << "\nsum_y: \t" << stats.sum_y 164 | << "\nssr_y: \t" << stats.ssr_y 165 | << "\nmean_y: \t" << stats.mean_y 166 | << "\nvariance_y: \t" << stats.variance_y 167 | << "\nsample variance_y:\t" << stats.sample_variance_y 168 | << "\ncorrelation: \t" << stats.correlation 169 | << "\ncovariance: \t" << stats.covariance 170 | << "\nsample covariance:\t" << stats.sample_covariance 171 | << "\n"; 172 | return os; 173 | } 174 | } // namespace VSTAT_NAMESPACE 175 | 176 | #endif 177 | -------------------------------------------------------------------------------- /include/vstat/combine.hpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // SPDX-FileCopyrightText: Copyright 2020-2024 Heal Research 3 | 4 | #ifndef VSTAT_COMBINE_HPP 5 | #define VSTAT_COMBINE_HPP 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "util.hpp" 15 | 16 | namespace VSTAT_NAMESPACE { 17 | 18 | namespace detail { 19 | template 20 | requires eve::simd_value 21 | inline auto unpack(T v) -> auto 22 | { 23 | return [&](std::index_sequence){ 24 | return std::array{ v.get(I) ... }; 25 | }(std::make_index_sequence{}); 26 | } 27 | } // namespace detail 28 | 29 | // The code below is based on: 30 | // Schubert et al. - Numerically Stable Parallel Computation of (Co-)Variance, p. 4, eq. 22-26 31 | // https://dbs.ifi.uni-heidelberg.de/files/Team/eschubert/publications/SSDBM18-covariance-authorcopy.pdf 32 | // merge covariance from individual data partitions A,B 33 | template 34 | requires eve::simd_value && (T::size() >= 2) 35 | inline auto combine(T sum_w, T sum_x, T sum_xx) -> double 36 | { 37 | if constexpr (T::size() == 2) { 38 | auto [n0, n1] = detail::unpack(sum_w); 39 | auto [s0, s1] = detail::unpack(sum_x); 40 | 41 | // this happens when we call stats on an accumulator that 42 | // hasn't yet processed any values and the weight is zero 43 | double f = 1. / (n0 * n1 * (n0 + n1)); 44 | if (!std::isfinite(f)) { f = 0; } 45 | return eve::reduce(sum_xx) + f * eve::sqr(n1 * s0 - n0 * s1); // eq. 22 46 | } else { 47 | auto [sum_w0, sum_w1] = sum_w.slice(); 48 | auto [sum_x0, sum_x1] = sum_x.slice(); 49 | auto [sum_xx0, sum_xx1] = sum_xx.slice(); 50 | 51 | double n0 = eve::reduce(sum_w0); 52 | double n1 = eve::reduce(sum_w1); 53 | 54 | double s0 = eve::reduce(sum_x0); 55 | double s1 = eve::reduce(sum_x1); 56 | 57 | double q0 = combine(sum_w0, sum_x0, sum_xx0); 58 | double q1 = combine(sum_w1, sum_x1, sum_xx1); 59 | 60 | double f = 1. / (n0 * n1 * (n0 + n1)); 61 | if (!std::isfinite(f)) { f = 0; } 62 | return q0 + q1 + f * eve::sqr(n1 * s0 - n0 * s1); // eq. 22 63 | } 64 | } 65 | 66 | template 67 | requires eve::simd_value && (T::size() >= 2) 68 | inline auto combine(T sum_w, T sum_x, T sum_y, T sum_xx, T sum_yy, T sum_xy) -> std::tuple // NOLINT 69 | { 70 | if constexpr (T::size() == 2) { 71 | auto [n0, n1] = detail::unpack(sum_w); 72 | auto [sx0, sx1] = detail::unpack(sum_x); 73 | auto [sy0, sy1] = detail::unpack(sum_y); 74 | auto [sxx0, sxx1] = detail::unpack(sum_xx); 75 | auto [syy0, syy1] = detail::unpack(sum_yy); 76 | auto [sxy0, sxy1] = detail::unpack(sum_xy); 77 | 78 | double f = 1. / (n0 * n1 * (n0 + n1)); 79 | if (!std::isfinite(f)) { f = 0; } 80 | 81 | double sx = n1 * sx0 - n0 * sx1; 82 | double sy = n1 * sy0 - n0 * sy1; 83 | double sxx = sxx0 + sxx1 + f * sx * sx; 84 | double syy = syy0 + syy1 + f * sy * sy; 85 | double sxy = sxy0 + sxy1 + f * sx * sy; 86 | 87 | return { sxx, syy, sxy }; 88 | } else { 89 | auto [sum_w0, sum_w1] = sum_w.slice(); 90 | auto [sum_x0, sum_x1] = sum_x.slice(); 91 | auto [sum_y0, sum_y1] = sum_y.slice(); 92 | auto [sum_xx0, sum_xx1] = sum_xx.slice(); 93 | auto [sum_yy0, sum_yy1] = sum_yy.slice(); 94 | auto [sum_xy0, sum_xy1] = sum_xy.slice(); 95 | 96 | auto [sxx0, syy0, sxy0] = combine(sum_w0, sum_x0, sum_y0, sum_xx0, sum_yy0, sum_xy0); 97 | auto [sxx1, syy1, sxy1] = combine(sum_w1, sum_x1, sum_y1, sum_xx1, sum_yy1, sum_xy1); 98 | 99 | double n0 = eve::reduce(sum_w0); 100 | double n1 = eve::reduce(sum_w1); 101 | double sx0 = eve::reduce(sum_x0); 102 | double sx1 = eve::reduce(sum_x1); 103 | double sy0 = eve::reduce(sum_y0); 104 | double sy1 = eve::reduce(sum_y1); 105 | 106 | double f = 1. / (n0 * n1 * (n0 + n1)); 107 | if (!std::isfinite(f)) { f = 0; } 108 | 109 | double sx = n1 * sx0 - n0 * sx1; 110 | double sy = n1 * sy0 - n0 * sy1; 111 | double sxx = sxx0 + sxx1 + f * sx * sx; 112 | double syy = syy0 + syy1 + f * sy * sy; 113 | double sxy = sxy0 + sxy1 + f * sx * sy; 114 | 115 | return { sxx, syy, sxy }; 116 | } 117 | } 118 | } // namespace VSTAT_NAMESPACE 119 | 120 | #endif 121 | -------------------------------------------------------------------------------- /include/vstat/univariate.hpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // SPDX-FileCopyrightText: Copyright 2020-2024 Heal Research 3 | 4 | #ifndef VSTAT_UNIVARIATE_HPP 5 | #define VSTAT_UNIVARIATE_HPP 6 | 7 | #include "combine.hpp" 8 | 9 | namespace VSTAT_NAMESPACE { 10 | /*! 11 | \brief Univariate accumulator object 12 | */ 13 | template 14 | struct univariate_accumulator { 15 | static auto load_state(T sw, T sx, T sxx) noexcept -> univariate_accumulator 16 | { 17 | univariate_accumulator acc; 18 | acc.sum_w = sw; 19 | acc.sum_w_old = sw; 20 | acc.sum_x = sx; 21 | acc.sum_xx = sxx; 22 | return acc; 23 | } 24 | 25 | static auto load_state(std::tuple state) noexcept -> univariate_accumulator 26 | { 27 | auto [sw, sx, sxx] = state; 28 | return load_state(sw, sx, sxx); 29 | } 30 | 31 | inline void operator()(T x) noexcept 32 | { 33 | T dx = sum_w * x - sum_x; 34 | sum_x += x; 35 | sum_w += 1; 36 | sum_xx += dx * dx / (sum_w * sum_w_old); 37 | sum_w_old = sum_w; 38 | } 39 | 40 | inline void operator()(T x, T w) noexcept 41 | { 42 | x *= w; 43 | T dx = sum_w * x - sum_x * w; 44 | sum_x += x; 45 | sum_w += w; 46 | sum_xx += dx * dx / (w * sum_w * sum_w_old); 47 | sum_w_old = sum_w; 48 | } 49 | 50 | template 51 | requires eve::simd_value && eve::simd_compatible_ptr 52 | inline void operator()(U const* x) noexcept 53 | { 54 | (*this)(T{x}); 55 | } 56 | 57 | template 58 | requires eve::simd_value && eve::simd_compatible_ptr 59 | inline void operator()(U const* x, U const* w) noexcept 60 | { 61 | (*this)(T{x}, T{w}); 62 | } 63 | 64 | // performs the reductions and returns { sum_w, sum_x, sum_xx } 65 | [[nodiscard]] auto stats() const noexcept -> std::tuple 66 | { 67 | if constexpr (std::is_floating_point_v) { 68 | return { sum_w, sum_x, sum_xx }; 69 | } else { 70 | return { eve::reduce(sum_w), eve::reduce(sum_x), combine(sum_w, sum_x, sum_xx) }; 71 | } 72 | } 73 | 74 | private: 75 | T sum_w{0}; 76 | T sum_w_old{1}; 77 | T sum_x{0}; 78 | T sum_xx{0}; 79 | }; 80 | 81 | /*! 82 | \brief Univariate statistics 83 | */ 84 | struct univariate_statistics { 85 | double count; 86 | double sum; 87 | double ssr; 88 | double mean; 89 | double variance; 90 | double sample_variance; 91 | 92 | template 93 | explicit univariate_statistics(T const& accumulator) 94 | { 95 | auto [sw, sx, sxx] = accumulator.stats(); 96 | count = sw; 97 | sum = sx; 98 | ssr = sxx; 99 | mean = sx / sw; 100 | variance = sxx / sw; 101 | sample_variance = sxx / (sw - 1); 102 | } 103 | }; 104 | 105 | inline auto operator<<(std::ostream& os, univariate_statistics const& stats) -> std::ostream& 106 | { 107 | os << "count: \t" << stats.count 108 | << "\nsum: \t" << stats.sum 109 | << "\nssr: \t" << stats.ssr 110 | << "\nmean: \t" << stats.mean 111 | << "\nvariance: \t" << stats.variance 112 | << "\nsample variance:\t" << stats.sample_variance 113 | << "\n"; 114 | return os; 115 | } 116 | 117 | } // namespace VSTAT_NAMESPACE 118 | 119 | #endif 120 | -------------------------------------------------------------------------------- /include/vstat/util.hpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // SPDX-FileCopyrightText: Copyright 2020-2023 Heal Research 3 | 4 | #ifndef VSTAT_UTIL_HPP 5 | #define VSTAT_UTIL_HPP 6 | 7 | #if defined(__GNUC__) || defined(__GNUG__) 8 | #define VSTAT_FORCE_INLINE __attribute__((always_inline)) inline 9 | #else 10 | #define VSTAT_FORCE_INLINE inline 11 | #endif 12 | 13 | #define VSTAT_EXPECT(cond) \ 14 | if (!(cond)) { \ 15 | std::cerr << "precondition " << #cond << " failed at " << __FILE__ << ": " << __LINE__ << "\n"; \ 16 | std::terminate(); \ 17 | } 18 | 19 | #define VSTAT_ENSURE(cond) \ 20 | if (!(cond)) { \ 21 | std::cerr << "postcondition " << #cond << " failed at " << __FILE__ << ": " << __LINE__ << "\n"; \ 22 | std::terminate(); \ 23 | } 24 | 25 | #if !defined(VSTAT_NAMESPACE) 26 | #define VSTAT_NAMESPACE vstat 27 | #endif 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /logo/vstat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "vstat" 3 | version = "1.0.0" 4 | authors = [ 5 | { name = "Bogdan Burlacu", email="bogdan.burlacu@pm.me" }, 6 | ] 7 | description = "vstat is a library for vectorized statistics" 8 | readme = "README.md" 9 | requires-python = ">=3.8" 10 | classifiers = [ 11 | "Development Status :: 4 - Beta", 12 | "License :: OSI Approved :: MIT License", 13 | "Programming Language :: Python :: 3", 14 | "Programming Language :: C++", 15 | "Intended Audience :: Science/Research", 16 | "Intended Audience :: Education", 17 | "Topic :: Scientific/Engineering :: Artificial Intelligence" 18 | ] 19 | 20 | [project.urls] 21 | "Homepage" = "https://github.com/heal-research/vstat" 22 | "Bug Tracker" = "https://github.com/heal-research/vstat/issues" 23 | 24 | [build-system] 25 | requires = [ 26 | "scikit-build>=0.16", 27 | "setuptools>=42", 28 | "cmake>=3.18", 29 | ] 30 | build-backend = "setuptools.build_meta" 31 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from skbuild import setup # This line replaces 'from setuptools import setup' 2 | import platform 3 | 4 | setup( 5 | name="vstat", 6 | version="1.0.0", 7 | description="vectorized statistics library", 8 | author='Bogdan Burlacu', 9 | packages = ['vstat'], 10 | python_requires=">=3.8", 11 | cmake_args=[f'-DCPM_USE_LOCAL_PACKAGES=1', '-Dvstat_BUILD_PYTHON=1'] 12 | ) 13 | -------------------------------------------------------------------------------- /src/vstat.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // SPDX-FileCopyrightText: Copyright 2019-2024 Heal Research 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace detail { 12 | template 13 | using array = nanobind::ndarray; 14 | 15 | // helpers 16 | template 17 | inline auto univariate_accumulate(std::span x) { 18 | return vstat::univariate::accumulate(x.begin(), x.end()); 19 | } 20 | 21 | template 22 | inline auto univariate_accumulate(std::span x, std::span w) { 23 | return vstat::univariate::accumulate(x.begin(), x.end(), w.begin()); 24 | } 25 | 26 | template 27 | inline auto bivariate_accumulate(std::span x, std::span y) { 28 | return vstat::bivariate::accumulate(x.begin(), x.end(), y.begin()); 29 | } 30 | 31 | template 32 | inline auto bivariate_accumulate(std::span x, std::span y, std::span w) { 33 | return vstat::bivariate::accumulate(x.begin(), x.end(), y.begin(), w.begin()); 34 | } 35 | } // namespace detail 36 | 37 | NB_MODULE(vstat, m) { // NOLINT 38 | namespace nb = nanobind; 39 | 40 | // objects that hold the statistical results 41 | nb::class_(m, "univariate_statistics") 42 | .def_ro("count", &vstat::univariate_statistics::count) 43 | .def_ro("sum", &vstat::univariate_statistics::sum) 44 | .def_ro("ssr", &vstat::univariate_statistics::ssr) 45 | .def_ro("mean", &vstat::univariate_statistics::mean) 46 | .def_ro("variance", &vstat::univariate_statistics::variance) 47 | .def_ro("sample_variance", &vstat::univariate_statistics::sample_variance); 48 | 49 | nb::class_(m, "bivariate_statistics") 50 | .def_ro("count", &vstat::bivariate_statistics::count) 51 | .def_ro("sum_x", &vstat::bivariate_statistics::sum_x) 52 | .def_ro("ssr_x", &vstat::bivariate_statistics::ssr_x) 53 | .def_ro("mean_x", &vstat::bivariate_statistics::mean_x) 54 | .def_ro("variance_x", &vstat::bivariate_statistics::variance_x) 55 | .def_ro("sample_variance_x", &vstat::bivariate_statistics::sample_variance_x) 56 | .def_ro("sum_y", &vstat::bivariate_statistics::sum_y) 57 | .def_ro("ssr_y", &vstat::bivariate_statistics::ssr_y) 58 | .def_ro("mean_y", &vstat::bivariate_statistics::mean_y) 59 | .def_ro("variance_y", &vstat::bivariate_statistics::variance_y) 60 | .def_ro("sample_variance_y", &vstat::bivariate_statistics::sample_variance_y) 61 | .def_ro("correlation", &vstat::bivariate_statistics::correlation) 62 | .def_ro("covariance", &vstat::bivariate_statistics::covariance) 63 | .def_ro("sample_covariance", &vstat::bivariate_statistics::sample_covariance); 64 | 65 | // single-precision (float) 66 | // univariate methods 67 | m.def("univariate_accumulate", [](detail::array x) { 68 | return detail::univariate_accumulate({x.data(), x.size()}); 69 | }); 70 | 71 | m.def("univariate_accumulate", [](std::vector const& x) { 72 | return detail::univariate_accumulate({x.data(), x.size()}); 73 | }); 74 | 75 | m.def("univariate_accumulate", [](detail::array x) { 76 | return detail::univariate_accumulate({x.data(), x.size()}); 77 | }); 78 | 79 | m.def("univariate_accumulate", [](std::vector const& x) { 80 | return detail::univariate_accumulate({x.data(), x.size()}); 81 | }); 82 | 83 | // mean 84 | m.def("mean", [](detail::array x) { 85 | return detail::univariate_accumulate({x.data(), x.size()}).mean; 86 | }); 87 | 88 | m.def("mean", [](detail::array x, detail::array w) { 89 | return detail::univariate_accumulate({x.data(), x.size()}, {w.data(), w.size()}); 90 | }); 91 | 92 | m.def("mean", [](std::vector const& x) { 93 | return detail::univariate_accumulate({x.data(), x.size()}).mean; 94 | }); 95 | 96 | m.def("mean", [](std::vector const& x, std::vector const& w) { 97 | return detail::univariate_accumulate({x.data(), x.size()}, {w.data(), w.size()}).mean; 98 | }); 99 | 100 | // variance 101 | m.def("variance", [](detail::array x) { 102 | return detail::univariate_accumulate({x.data(), x.size()}).variance; 103 | }); 104 | 105 | m.def("variance", [](detail::array x, detail::array w) { 106 | return detail::univariate_accumulate({x.data(), x.size()}, {w.data(), w.size()}); 107 | }); 108 | 109 | m.def("variance", [](std::vector const& x) { 110 | return detail::univariate_accumulate({x.data(), x.size()}).variance; 111 | }); 112 | 113 | m.def("variance", [](std::vector const& x, std::vector const& w) { 114 | return detail::univariate_accumulate({x.data(), x.size()}, {w.data(), w.size()}).variance; 115 | }); 116 | 117 | // sample variance 118 | m.def("sample_variance", [](detail::array x) { 119 | return detail::univariate_accumulate({x.data(), x.size()}).sample_variance; 120 | }); 121 | 122 | m.def("sample_variance", [](detail::array x, detail::array w) { 123 | return detail::univariate_accumulate({x.data(), x.size()}, {w.data(), w.size()}); 124 | }); 125 | 126 | m.def("sample_variance", [](std::vector const& x) { 127 | return detail::univariate_accumulate({x.data(), x.size()}).sample_variance; 128 | }); 129 | 130 | m.def("sample_variance", [](std::vector const& x, std::vector const& w) { 131 | return detail::univariate_accumulate({x.data(), x.size()}, {w.data(), w.size()}).sample_variance; 132 | }); 133 | 134 | // bivariate methods 135 | // covariance 136 | m.def("covariance", [](detail::array x, detail::array y) { 137 | return detail::bivariate_accumulate({x.data(), x.size()}, {y.data(), y.size()}).covariance; 138 | }); 139 | 140 | m.def("covariance", [](detail::array x, detail::array y, detail::array w) { 141 | return detail::bivariate_accumulate({x.data(), x.size()}, {y.data(), y.size()}, {w.data(), w.size()}).covariance; 142 | }); 143 | 144 | m.def("covariance", [](std::vector const& x, std::vector const& y) { 145 | return detail::bivariate_accumulate({x.data(), x.size()}, {y.data(), y.size()}).covariance; 146 | }); 147 | 148 | m.def("covariance", [](std::vector const& x, std::vector const& y, std::vector const& w) { 149 | return detail::bivariate_accumulate({x.data(), x.size()}, {y.data(), y.size()}, {w.data(), w.size()}).covariance; 150 | }); 151 | 152 | m.def("sample_covariance", [](detail::array x, detail::array y) { 153 | return detail::bivariate_accumulate({x.data(), x.size()}, {y.data(), y.size()}).sample_covariance; 154 | }); 155 | 156 | m.def("sample_covariance", [](detail::array x, detail::array y, detail::array w) { 157 | return detail::bivariate_accumulate({x.data(), x.size()}, {y.data(), y.size()}, {w.data(), w.size()}).sample_covariance; 158 | }); 159 | 160 | m.def("sample_covariance", [](std::vector const& x, std::vector const& y) { 161 | return detail::bivariate_accumulate({x.data(), x.size()}, {y.data(), y.size()}).sample_covariance; 162 | }); 163 | 164 | m.def("sample_covariance", [](std::vector const& x, std::vector const& y, std::vector const& w) { 165 | return detail::bivariate_accumulate({x.data(), x.size()}, {y.data(), y.size()}, {w.data(), w.size()}).sample_covariance; 166 | }); 167 | 168 | m.def("correlation", [](detail::array x, detail::array y) { 169 | return detail::bivariate_accumulate({x.data(), x.size()}, {y.data(), y.size()}).correlation; 170 | }); 171 | 172 | m.def("correlation", [](detail::array x, detail::array y, detail::array w) { 173 | return detail::bivariate_accumulate({x.data(), x.size()}, {y.data(), y.size()}, {w.data(), w.size()}).correlation; 174 | }); 175 | 176 | m.def("correlation", [](std::vector const& x, std::vector const& y) { 177 | return detail::bivariate_accumulate({x.data(), x.size()}, {y.data(), y.size()}).correlation; 178 | }); 179 | 180 | m.def("correlation", [](std::vector const& x, std::vector const& y, std::vector const& w) { 181 | return detail::bivariate_accumulate({x.data(), x.size()}, {y.data(), y.size()}, {w.data(), w.size()}).correlation; 182 | }); 183 | 184 | // metrics 185 | // mean absolute error 186 | m.def("mean_absolute_error", [](detail::array x, detail::array y) { 187 | std::span a{x.data(), x.size()}; 188 | std::span b{y.data(), y.size()}; 189 | return vstat::metrics::mean_absolute_error(a.begin(), a.end(), b.begin()); 190 | }); 191 | 192 | m.def("mean_absolute_error", [](detail::array x, detail::array y, detail::array w) { 193 | std::span a{x.data(), x.size()}; 194 | std::span b{y.data(), y.size()}; 195 | std::span c{w.data(), w.size()}; 196 | return vstat::metrics::mean_absolute_error(a.begin(), a.end(), b.begin(), c.begin()); 197 | }); 198 | 199 | m.def("mean_absolute_error", [](std::vector const& x, std::vector const& y) { 200 | std::span a{x.data(), x.size()}; 201 | std::span b{y.data(), y.size()}; 202 | return vstat::metrics::mean_absolute_error(a.begin(), a.end(), b.begin()); 203 | }); 204 | 205 | m.def("mean_absolute_error", [](std::vector const& x, std::vector const& y, std::vector const& w) { 206 | std::span a{x.data(), x.size()}; 207 | std::span b{y.data(), y.size()}; 208 | std::span c{w.data(), w.size()}; 209 | return vstat::metrics::mean_absolute_error(a.begin(), a.end(), b.begin(), c.begin()); 210 | }); 211 | 212 | // mean absolute percentage error 213 | m.def("mean_absolute_percentage_error", [](detail::array x, detail::array y) { 214 | std::span a{x.data(), x.size()}; 215 | std::span b{y.data(), y.size()}; 216 | return vstat::metrics::mean_absolute_percentage_error(a.begin(), a.end(), b.begin()); 217 | }); 218 | 219 | m.def("mean_absolute_percentage_error", [](detail::array x, detail::array y, detail::array w) { 220 | std::span a{x.data(), x.size()}; 221 | std::span b{y.data(), y.size()}; 222 | std::span c{w.data(), w.size()}; 223 | return vstat::metrics::mean_absolute_percentage_error(a.begin(), a.end(), b.begin(), c.begin()); 224 | }); 225 | 226 | m.def("mean_absolute_percentage_error", [](std::vector const& x, std::vector const& y) { 227 | std::span a{x.data(), x.size()}; 228 | std::span b{y.data(), y.size()}; 229 | return vstat::metrics::mean_absolute_percentage_error(a.begin(), a.end(), b.begin()); 230 | }); 231 | 232 | m.def("mean_absolute_percentage_error", [](std::vector const& x, std::vector const& y, std::vector const& w) { 233 | std::span a{x.data(), x.size()}; 234 | std::span b{y.data(), y.size()}; 235 | std::span c{w.data(), w.size()}; 236 | return vstat::metrics::mean_absolute_percentage_error(a.begin(), a.end(), b.begin(), c.begin()); 237 | }); 238 | 239 | // mean squared error 240 | m.def("mean_squared_error", [](detail::array x, detail::array y) { 241 | std::span a{x.data(), x.size()}; 242 | std::span b{y.data(), y.size()}; 243 | return vstat::metrics::mean_squared_error(a.begin(), a.end(), b.begin()); 244 | }); 245 | 246 | m.def("mean_squared_error", [](detail::array x, detail::array y, detail::array w) { 247 | std::span a{x.data(), x.size()}; 248 | std::span b{y.data(), y.size()}; 249 | std::span c{w.data(), w.size()}; 250 | return vstat::metrics::mean_squared_error(a.begin(), a.end(), b.begin(), c.begin()); 251 | }); 252 | 253 | m.def("mean_squared_error", [](std::vector const& x, std::vector const& y) { 254 | std::span a{x.data(), x.size()}; 255 | std::span b{y.data(), y.size()}; 256 | return vstat::metrics::mean_squared_error(a.begin(), a.end(), b.begin()); 257 | }); 258 | 259 | m.def("mean_squared_error", [](std::vector const& x, std::vector const& y, std::vector const& w) { 260 | std::span a{x.data(), x.size()}; 261 | std::span b{y.data(), y.size()}; 262 | std::span c{w.data(), w.size()}; 263 | return vstat::metrics::mean_squared_error(a.begin(), a.end(), b.begin(), c.begin()); 264 | }); 265 | 266 | // mean squared log error 267 | m.def("mean_squared_log_error", [](detail::array x, detail::array y) { 268 | std::span a{x.data(), x.size()}; 269 | std::span b{y.data(), y.size()}; 270 | return vstat::metrics::mean_squared_log_error(a.begin(), a.end(), b.begin()); 271 | }); 272 | 273 | m.def("mean_squared_log_error", [](detail::array x, detail::array y, detail::array w) { 274 | std::span a{x.data(), x.size()}; 275 | std::span b{y.data(), y.size()}; 276 | std::span c{w.data(), w.size()}; 277 | return vstat::metrics::mean_squared_log_error(a.begin(), a.end(), b.begin(), c.begin()); 278 | }); 279 | 280 | m.def("mean_squared_log_error", [](std::vector const& x, std::vector const& y) { 281 | std::span a{x.data(), x.size()}; 282 | std::span b{y.data(), y.size()}; 283 | return vstat::metrics::mean_squared_log_error(a.begin(), a.end(), b.begin()); 284 | }); 285 | 286 | m.def("mean_squared_log_error", [](std::vector const& x, std::vector const& y, std::vector const& w) { 287 | std::span a{x.data(), x.size()}; 288 | std::span b{y.data(), y.size()}; 289 | std::span c{w.data(), w.size()}; 290 | return vstat::metrics::mean_squared_log_error(a.begin(), a.end(), b.begin(), c.begin()); 291 | }); 292 | 293 | // r2 score 294 | m.def("r2_score", [](detail::array x, detail::array y) { 295 | std::span a{x.data(), x.size()}; 296 | std::span b{y.data(), y.size()}; 297 | return vstat::metrics::r2_score(a.begin(), a.end(), b.begin()); 298 | }); 299 | 300 | m.def("r2_score", [](detail::array x, detail::array y, detail::array w) { 301 | std::span a{x.data(), x.size()}; 302 | std::span b{y.data(), y.size()}; 303 | std::span c{w.data(), w.size()}; 304 | return vstat::metrics::r2_score(a.begin(), a.end(), b.begin(), c.begin()); 305 | }); 306 | 307 | m.def("r2_score", [](std::vector const& x, std::vector const& y) { 308 | std::span a{x.data(), x.size()}; 309 | std::span b{y.data(), y.size()}; 310 | return vstat::metrics::r2_score(a.begin(), a.end(), b.begin()); 311 | }); 312 | 313 | m.def("r2_score", [](std::vector const& x, std::vector const& y, std::vector const& w) { 314 | std::span a{x.data(), x.size()}; 315 | std::span b{y.data(), y.size()}; 316 | std::span c{w.data(), w.size()}; 317 | return vstat::metrics::r2_score(a.begin(), a.end(), b.begin(), c.begin()); 318 | }); 319 | 320 | // poisson loss 321 | m.def("poisson_neg_likelihood_loss", [](detail::array x, detail::array y) { 322 | std::span a{x.data(), x.size()}; 323 | std::span b{y.data(), y.size()}; 324 | return vstat::metrics::poisson_neg_likelihood_loss(a.begin(), a.end(), b.begin()); 325 | }); 326 | 327 | m.def("poisson_neg_likelihood_loss", [](detail::array x, detail::array y, detail::array w) { 328 | std::span a{x.data(), x.size()}; 329 | std::span b{y.data(), y.size()}; 330 | std::span c{w.data(), w.size()}; 331 | return vstat::metrics::poisson_neg_likelihood_loss(a.begin(), a.end(), b.begin(), c.begin()); 332 | }); 333 | 334 | m.def("poisson_neg_likelihood_loss", [](std::vector const& x, std::vector const& y) { 335 | std::span a{x.data(), x.size()}; 336 | std::span b{y.data(), y.size()}; 337 | return vstat::metrics::poisson_neg_likelihood_loss(a.begin(), a.end(), b.begin()); 338 | }); 339 | 340 | m.def("poisson_neg_likelihood_loss", [](std::vector const& x, std::vector const& y, std::vector const& w) { 341 | std::span a{x.data(), x.size()}; 342 | std::span b{y.data(), y.size()}; 343 | std::span c{w.data(), w.size()}; 344 | return vstat::metrics::poisson_neg_likelihood_loss(a.begin(), a.end(), b.begin(), c.begin()); 345 | }); 346 | 347 | // double-precision (double) 348 | // univariate methods 349 | m.def("univariate_accumulate", [](detail::array x) { 350 | return detail::univariate_accumulate({x.data(), x.size()}); 351 | }); 352 | 353 | m.def("univariate_accumulate", [](std::vector const& x) { 354 | return detail::univariate_accumulate({x.data(), x.size()}); 355 | }); 356 | 357 | m.def("univariate_accumulate", [](detail::array x) { 358 | return detail::univariate_accumulate({x.data(), x.size()}); 359 | }); 360 | 361 | m.def("univariate_accumulate", [](std::vector const& x) { 362 | return detail::univariate_accumulate({x.data(), x.size()}); 363 | }); 364 | 365 | // mean 366 | m.def("mean", [](detail::array x) { 367 | return detail::univariate_accumulate({x.data(), x.size()}).mean; 368 | }); 369 | 370 | m.def("mean", [](detail::array x, detail::array w) { 371 | return detail::univariate_accumulate({x.data(), x.size()}, {w.data(), w.size()}); 372 | }); 373 | 374 | m.def("mean", [](std::vector const& x) { 375 | return detail::univariate_accumulate({x.data(), x.size()}).mean; 376 | }); 377 | 378 | m.def("mean", [](std::vector const& x, std::vector const& w) { 379 | return detail::univariate_accumulate({x.data(), x.size()}, {w.data(), w.size()}).mean; 380 | }); 381 | 382 | // variance 383 | m.def("variance", [](detail::array x) { 384 | return detail::univariate_accumulate({x.data(), x.size()}).variance; 385 | }); 386 | 387 | m.def("variance", [](detail::array x, detail::array w) { 388 | return detail::univariate_accumulate({x.data(), x.size()}, {w.data(), w.size()}); 389 | }); 390 | 391 | m.def("variance", [](std::vector const& x) { 392 | return detail::univariate_accumulate({x.data(), x.size()}).variance; 393 | }); 394 | 395 | m.def("variance", [](std::vector const& x, std::vector const& w) { 396 | return detail::univariate_accumulate({x.data(), x.size()}, {w.data(), w.size()}).variance; 397 | }); 398 | 399 | // sample variance 400 | m.def("sample_variance", [](detail::array x) { 401 | return detail::univariate_accumulate({x.data(), x.size()}).sample_variance; 402 | }); 403 | 404 | m.def("sample_variance", [](detail::array x, detail::array w) { 405 | return detail::univariate_accumulate({x.data(), x.size()}, {w.data(), w.size()}); 406 | }); 407 | 408 | m.def("sample_variance", [](std::vector const& x) { 409 | return detail::univariate_accumulate({x.data(), x.size()}).sample_variance; 410 | }); 411 | 412 | m.def("sample_variance", [](std::vector const& x, std::vector const& w) { 413 | return detail::univariate_accumulate({x.data(), x.size()}, {w.data(), w.size()}).sample_variance; 414 | }); 415 | 416 | // bivariate methods 417 | // covariance 418 | m.def("covariance", [](detail::array x, detail::array y) { 419 | return detail::bivariate_accumulate({x.data(), x.size()}, {y.data(), y.size()}).covariance; 420 | }); 421 | 422 | m.def("covariance", [](detail::array x, detail::array y, detail::array w) { 423 | return detail::bivariate_accumulate({x.data(), x.size()}, {y.data(), y.size()}, {w.data(), w.size()}).covariance; 424 | }); 425 | 426 | m.def("covariance", [](std::vector const& x, std::vector const& y) { 427 | return detail::bivariate_accumulate({x.data(), x.size()}, {y.data(), y.size()}).covariance; 428 | }); 429 | 430 | m.def("covariance", [](std::vector const& x, std::vector const& y, std::vector const& w) { 431 | return detail::bivariate_accumulate({x.data(), x.size()}, {y.data(), y.size()}, {w.data(), w.size()}).covariance; 432 | }); 433 | 434 | m.def("sample_covariance", [](detail::array x, detail::array y) { 435 | return detail::bivariate_accumulate({x.data(), x.size()}, {y.data(), y.size()}).sample_covariance; 436 | }); 437 | 438 | m.def("sample_covariance", [](detail::array x, detail::array y, detail::array w) { 439 | return detail::bivariate_accumulate({x.data(), x.size()}, {y.data(), y.size()}, {w.data(), w.size()}).sample_covariance; 440 | }); 441 | 442 | m.def("sample_covariance", [](std::vector const& x, std::vector const& y) { 443 | return detail::bivariate_accumulate({x.data(), x.size()}, {y.data(), y.size()}).sample_covariance; 444 | }); 445 | 446 | m.def("sample_covariance", [](std::vector const& x, std::vector const& y, std::vector const& w) { 447 | return detail::bivariate_accumulate({x.data(), x.size()}, {y.data(), y.size()}, {w.data(), w.size()}).sample_covariance; 448 | }); 449 | 450 | m.def("correlation", [](detail::array x, detail::array y) { 451 | return detail::bivariate_accumulate({x.data(), x.size()}, {y.data(), y.size()}).correlation; 452 | }); 453 | 454 | m.def("correlation", [](detail::array x, detail::array y, detail::array w) { 455 | return detail::bivariate_accumulate({x.data(), x.size()}, {y.data(), y.size()}, {w.data(), w.size()}).correlation; 456 | }); 457 | 458 | m.def("correlation", [](std::vector const& x, std::vector const& y) { 459 | return detail::bivariate_accumulate({x.data(), x.size()}, {y.data(), y.size()}).correlation; 460 | }); 461 | 462 | m.def("correlation", [](std::vector const& x, std::vector const& y, std::vector const& w) { 463 | return detail::bivariate_accumulate({x.data(), x.size()}, {y.data(), y.size()}, {w.data(), w.size()}).correlation; 464 | }); 465 | 466 | // metrics 467 | // mean absolute error 468 | m.def("mean_absolute_error", [](detail::array x, detail::array y) { 469 | std::span a{x.data(), x.size()}; 470 | std::span b{y.data(), y.size()}; 471 | return vstat::metrics::mean_absolute_error(a.begin(), a.end(), b.begin()); 472 | }); 473 | 474 | m.def("mean_absolute_error", [](detail::array x, detail::array y, detail::array w) { 475 | std::span a{x.data(), x.size()}; 476 | std::span b{y.data(), y.size()}; 477 | std::span c{w.data(), w.size()}; 478 | return vstat::metrics::mean_absolute_error(a.begin(), a.end(), b.begin(), c.begin()); 479 | }); 480 | 481 | m.def("mean_absolute_error", [](std::vector const& x, std::vector const& y) { 482 | std::span a{x.data(), x.size()}; 483 | std::span b{y.data(), y.size()}; 484 | return vstat::metrics::mean_absolute_error(a.begin(), a.end(), b.begin()); 485 | }); 486 | 487 | m.def("mean_absolute_error", [](std::vector const& x, std::vector const& y, std::vector const& w) { 488 | std::span a{x.data(), x.size()}; 489 | std::span b{y.data(), y.size()}; 490 | std::span c{w.data(), w.size()}; 491 | return vstat::metrics::mean_absolute_error(a.begin(), a.end(), b.begin(), c.begin()); 492 | }); 493 | 494 | // mean absolute percentage error 495 | m.def("mean_absolute_percentage_error", [](detail::array x, detail::array y) { 496 | std::span a{x.data(), x.size()}; 497 | std::span b{y.data(), y.size()}; 498 | return vstat::metrics::mean_absolute_percentage_error(a.begin(), a.end(), b.begin()); 499 | }); 500 | 501 | m.def("mean_absolute_percentage_error", [](detail::array x, detail::array y, detail::array w) { 502 | std::span a{x.data(), x.size()}; 503 | std::span b{y.data(), y.size()}; 504 | std::span c{w.data(), w.size()}; 505 | return vstat::metrics::mean_absolute_percentage_error(a.begin(), a.end(), b.begin(), c.begin()); 506 | }); 507 | 508 | m.def("mean_absolute_percentage_error", [](std::vector const& x, std::vector const& y) { 509 | std::span a{x.data(), x.size()}; 510 | std::span b{y.data(), y.size()}; 511 | return vstat::metrics::mean_absolute_percentage_error(a.begin(), a.end(), b.begin()); 512 | }); 513 | 514 | m.def("mean_absolute_percentage_error", [](std::vector const& x, std::vector const& y, std::vector const& w) { 515 | std::span a{x.data(), x.size()}; 516 | std::span b{y.data(), y.size()}; 517 | std::span c{w.data(), w.size()}; 518 | return vstat::metrics::mean_absolute_percentage_error(a.begin(), a.end(), b.begin(), c.begin()); 519 | }); 520 | 521 | // mean squared error 522 | m.def("mean_squared_error", [](detail::array x, detail::array y) { 523 | std::span a{x.data(), x.size()}; 524 | std::span b{y.data(), y.size()}; 525 | return vstat::metrics::mean_squared_error(a.begin(), a.end(), b.begin()); 526 | }); 527 | 528 | m.def("mean_squared_error", [](detail::array x, detail::array y, detail::array w) { 529 | std::span a{x.data(), x.size()}; 530 | std::span b{y.data(), y.size()}; 531 | std::span c{w.data(), w.size()}; 532 | return vstat::metrics::mean_squared_error(a.begin(), a.end(), b.begin(), c.begin()); 533 | }); 534 | 535 | m.def("mean_squared_error", [](std::vector const& x, std::vector const& y) { 536 | std::span a{x.data(), x.size()}; 537 | std::span b{y.data(), y.size()}; 538 | return vstat::metrics::mean_squared_error(a.begin(), a.end(), b.begin()); 539 | }); 540 | 541 | m.def("mean_squared_error", [](std::vector const& x, std::vector const& y, std::vector const& w) { 542 | std::span a{x.data(), x.size()}; 543 | std::span b{y.data(), y.size()}; 544 | std::span c{w.data(), w.size()}; 545 | return vstat::metrics::mean_squared_error(a.begin(), a.end(), b.begin(), c.begin()); 546 | }); 547 | 548 | // mean squared log error 549 | m.def("mean_squared_log_error", [](detail::array x, detail::array y) { 550 | std::span a{x.data(), x.size()}; 551 | std::span b{y.data(), y.size()}; 552 | return vstat::metrics::mean_squared_log_error(a.begin(), a.end(), b.begin()); 553 | }); 554 | 555 | m.def("mean_squared_log_error", [](detail::array x, detail::array y, detail::array w) { 556 | std::span a{x.data(), x.size()}; 557 | std::span b{y.data(), y.size()}; 558 | std::span c{w.data(), w.size()}; 559 | return vstat::metrics::mean_squared_log_error(a.begin(), a.end(), b.begin(), c.begin()); 560 | }); 561 | 562 | m.def("mean_squared_log_error", [](std::vector const& x, std::vector const& y) { 563 | std::span a{x.data(), x.size()}; 564 | std::span b{y.data(), y.size()}; 565 | return vstat::metrics::mean_squared_log_error(a.begin(), a.end(), b.begin()); 566 | }); 567 | 568 | m.def("mean_squared_log_error", [](std::vector const& x, std::vector const& y, std::vector const& w) { 569 | std::span a{x.data(), x.size()}; 570 | std::span b{y.data(), y.size()}; 571 | std::span c{w.data(), w.size()}; 572 | return vstat::metrics::mean_squared_log_error(a.begin(), a.end(), b.begin(), c.begin()); 573 | }); 574 | 575 | // r2 score 576 | m.def("r2_score", [](detail::array x, detail::array y) { 577 | std::span a{x.data(), x.size()}; 578 | std::span b{y.data(), y.size()}; 579 | return vstat::metrics::r2_score(a.begin(), a.end(), b.begin()); 580 | }); 581 | 582 | m.def("r2_score", [](detail::array x, detail::array y, detail::array w) { 583 | std::span a{x.data(), x.size()}; 584 | std::span b{y.data(), y.size()}; 585 | std::span c{w.data(), w.size()}; 586 | return vstat::metrics::r2_score(a.begin(), a.end(), b.begin(), c.begin()); 587 | }); 588 | 589 | m.def("r2_score", [](std::vector const& x, std::vector const& y) { 590 | std::span a{x.data(), x.size()}; 591 | std::span b{y.data(), y.size()}; 592 | return vstat::metrics::r2_score(a.begin(), a.end(), b.begin()); 593 | }); 594 | 595 | m.def("r2_score", [](std::vector const& x, std::vector const& y, std::vector const& w) { 596 | std::span a{x.data(), x.size()}; 597 | std::span b{y.data(), y.size()}; 598 | std::span c{w.data(), w.size()}; 599 | return vstat::metrics::r2_score(a.begin(), a.end(), b.begin(), c.begin()); 600 | }); 601 | 602 | // poisson loss 603 | m.def("poisson_neg_likelihood_loss", [](detail::array x, detail::array y) { 604 | std::span a{x.data(), x.size()}; 605 | std::span b{y.data(), y.size()}; 606 | return vstat::metrics::poisson_neg_likelihood_loss(a.begin(), a.end(), b.begin()); 607 | }); 608 | 609 | m.def("poisson_neg_likelihood_loss", [](detail::array x, detail::array y, detail::array w) { 610 | std::span a{x.data(), x.size()}; 611 | std::span b{y.data(), y.size()}; 612 | std::span c{w.data(), w.size()}; 613 | return vstat::metrics::poisson_neg_likelihood_loss(a.begin(), a.end(), b.begin(), c.begin()); 614 | }); 615 | 616 | m.def("poisson_neg_likelihood_loss", [](std::vector const& x, std::vector const& y) { 617 | std::span a{x.data(), x.size()}; 618 | std::span b{y.data(), y.size()}; 619 | return vstat::metrics::poisson_neg_likelihood_loss(a.begin(), a.end(), b.begin()); 620 | }); 621 | 622 | m.def("poisson_neg_likelihood_loss", [](std::vector const& x, std::vector const& y, std::vector const& w) { 623 | std::span a{x.data(), x.size()}; 624 | std::span b{y.data(), y.size()}; 625 | std::span c{w.data(), w.size()}; 626 | return vstat::metrics::poisson_neg_likelihood_loss(a.begin(), a.end(), b.begin(), c.begin()); 627 | }); 628 | } 629 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(vstatTests LANGUAGES CXX) 4 | 5 | include(../cmake/project-is-top-level.cmake) 6 | include(../cmake/windows-set-path.cmake) 7 | 8 | if(PROJECT_IS_TOP_LEVEL) 9 | find_package(vstat REQUIRED) 10 | enable_testing() 11 | endif() 12 | 13 | add_executable(vstat_test source/vstat_test.cpp) 14 | 15 | 16 | # ---- Doctest ---- 17 | find_package(doctest REQUIRED) 18 | 19 | # ---- GNU Scientific Library ---- 20 | find_package(GSL REQUIRED) 21 | 22 | # ---- Boost Accumulators ---- 23 | find_package(Boost REQUIRED) 24 | 25 | # ---- Linasm ---- 26 | find_package(PkgConfig REQUIRED) 27 | pkg_check_modules(linasm IMPORTED_TARGET linasm) 28 | if(linasm_FOUND) 29 | target_link_libraries(vstat_test PRIVATE PkgConfig::linasm) 30 | else() 31 | message(FATAL_ERROR "LinAsm dependency could not be found.") 32 | endif() 33 | 34 | target_link_libraries(vstat_test PRIVATE vstat::vstat GSL::gsl doctest::doctest) 35 | target_compile_features(vstat_test PRIVATE cxx_std_20) 36 | 37 | if(MSVC) 38 | target_compile_options(vstat_test PUBLIC "$<$:/O2;/std:c++latest>") 39 | else() 40 | target_compile_options(vstat_test PUBLIC "$<$:-fno-math-errno>") 41 | endif() 42 | 43 | include(doctest) 44 | doctest_discover_tests(vstat_test) 45 | -------------------------------------------------------------------------------- /test/benchmarks/cov_double.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heal-research/vstat/413cbd8198613099952c5fd2cb0e696d575af467/test/benchmarks/cov_double.png -------------------------------------------------------------------------------- /test/benchmarks/cov_float.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heal-research/vstat/413cbd8198613099952c5fd2cb0e696d575af467/test/benchmarks/cov_float.png -------------------------------------------------------------------------------- /test/benchmarks/var_double.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heal-research/vstat/413cbd8198613099952c5fd2cb0e696d575af467/test/benchmarks/var_double.png -------------------------------------------------------------------------------- /test/benchmarks/var_float.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heal-research/vstat/413cbd8198613099952c5fd2cb0e696d575af467/test/benchmarks/var_float.png -------------------------------------------------------------------------------- /test/cmake/doctest.cmake: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved BSD 3-Clause License. See accompanying 2 | # file Copyright.txt or https://cmake.org/licensing for details. 3 | 4 | #[=======================================================================[.rst: 5 | doctest 6 | ----- 7 | 8 | This module defines a function to help use the doctest test framework. 9 | 10 | The :command:`doctest_discover_tests` discovers tests by asking the compiled test 11 | executable to enumerate its tests. This does not require CMake to be re-run 12 | when tests change. However, it may not work in a cross-compiling environment, 13 | and setting test properties is less convenient. 14 | 15 | This command is intended to replace use of :command:`add_test` to register 16 | tests, and will create a separate CTest test for each doctest test case. Note 17 | that this is in some cases less efficient, as common set-up and tear-down logic 18 | cannot be shared by multiple test cases executing in the same instance. 19 | However, it provides more fine-grained pass/fail information to CTest, which is 20 | usually considered as more beneficial. By default, the CTest test name is the 21 | same as the doctest name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``. 22 | 23 | .. command:: doctest_discover_tests 24 | 25 | Automatically add tests with CTest by querying the compiled test executable 26 | for available tests:: 27 | 28 | doctest_discover_tests(target 29 | [TEST_SPEC arg1...] 30 | [EXTRA_ARGS arg1...] 31 | [WORKING_DIRECTORY dir] 32 | [TEST_PREFIX prefix] 33 | [TEST_SUFFIX suffix] 34 | [PROPERTIES name1 value1...] 35 | [ADD_LABELS value] 36 | [TEST_LIST var] 37 | [JUNIT_OUTPUT_DIR dir] 38 | ) 39 | 40 | ``doctest_discover_tests`` sets up a post-build command on the test executable 41 | that generates the list of tests by parsing the output from running the test 42 | with the ``--list-test-cases`` argument. This ensures that the full 43 | list of tests is obtained. Since test discovery occurs at build time, it is 44 | not necessary to re-run CMake when the list of tests changes. 45 | However, it requires that :prop_tgt:`CROSSCOMPILING_EMULATOR` is properly set 46 | in order to function in a cross-compiling environment. 47 | 48 | Additionally, setting properties on tests is somewhat less convenient, since 49 | the tests are not available at CMake time. Additional test properties may be 50 | assigned to the set of tests as a whole using the ``PROPERTIES`` option. If 51 | more fine-grained test control is needed, custom content may be provided 52 | through an external CTest script using the :prop_dir:`TEST_INCLUDE_FILES` 53 | directory property. The set of discovered tests is made accessible to such a 54 | script via the ``_TESTS`` variable. 55 | 56 | The options are: 57 | 58 | ``target`` 59 | Specifies the doctest executable, which must be a known CMake executable 60 | target. CMake will substitute the location of the built executable when 61 | running the test. 62 | 63 | ``TEST_SPEC arg1...`` 64 | Specifies test cases, wildcarded test cases, tags and tag expressions to 65 | pass to the doctest executable with the ``--list-test-cases`` argument. 66 | 67 | ``EXTRA_ARGS arg1...`` 68 | Any extra arguments to pass on the command line to each test case. 69 | 70 | ``WORKING_DIRECTORY dir`` 71 | Specifies the directory in which to run the discovered test cases. If this 72 | option is not provided, the current binary directory is used. 73 | 74 | ``TEST_PREFIX prefix`` 75 | Specifies a ``prefix`` to be prepended to the name of each discovered test 76 | case. This can be useful when the same test executable is being used in 77 | multiple calls to ``doctest_discover_tests()`` but with different 78 | ``TEST_SPEC`` or ``EXTRA_ARGS``. 79 | 80 | ``TEST_SUFFIX suffix`` 81 | Similar to ``TEST_PREFIX`` except the ``suffix`` is appended to the name of 82 | every discovered test case. Both ``TEST_PREFIX`` and ``TEST_SUFFIX`` may 83 | be specified. 84 | 85 | ``PROPERTIES name1 value1...`` 86 | Specifies additional properties to be set on all tests discovered by this 87 | invocation of ``doctest_discover_tests``. 88 | 89 | ``ADD_LABELS value`` 90 | Specifies if the test labels should be set automatically. 91 | 92 | ``TEST_LIST var`` 93 | Make the list of tests available in the variable ``var``, rather than the 94 | default ``_TESTS``. This can be useful when the same test 95 | executable is being used in multiple calls to ``doctest_discover_tests()``. 96 | Note that this variable is only available in CTest. 97 | 98 | ``JUNIT_OUTPUT_DIR dir`` 99 | If specified, the parameter is passed along with ``--reporters=junit`` 100 | and ``--out=`` to the test executable. The actual file name is the same 101 | as the test target, including prefix and suffix. This should be used 102 | instead of EXTRA_ARGS to avoid race conditions writing the XML result 103 | output when using parallel test execution. 104 | 105 | #]=======================================================================] 106 | 107 | #------------------------------------------------------------------------------ 108 | function(doctest_discover_tests TARGET) 109 | cmake_parse_arguments( 110 | "" 111 | "" 112 | "TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST;JUNIT_OUTPUT_DIR" 113 | "TEST_SPEC;EXTRA_ARGS;PROPERTIES;ADD_LABELS" 114 | ${ARGN} 115 | ) 116 | 117 | if(NOT _WORKING_DIRECTORY) 118 | set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") 119 | endif() 120 | if(NOT _TEST_LIST) 121 | set(_TEST_LIST ${TARGET}_TESTS) 122 | endif() 123 | 124 | ## Generate a unique name based on the extra arguments 125 | string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS}") 126 | string(SUBSTRING ${args_hash} 0 7 args_hash) 127 | 128 | # Define rule to generate test list for aforementioned test executable 129 | set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake") 130 | set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake") 131 | get_property(crosscompiling_emulator 132 | TARGET ${TARGET} 133 | PROPERTY CROSSCOMPILING_EMULATOR 134 | ) 135 | add_custom_command( 136 | TARGET ${TARGET} POST_BUILD 137 | BYPRODUCTS "${ctest_tests_file}" 138 | COMMAND "${CMAKE_COMMAND}" 139 | -D "TEST_TARGET=${TARGET}" 140 | -D "TEST_EXECUTABLE=$" 141 | -D "TEST_EXECUTOR=${crosscompiling_emulator}" 142 | -D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}" 143 | -D "TEST_SPEC=${_TEST_SPEC}" 144 | -D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}" 145 | -D "TEST_PROPERTIES=${_PROPERTIES}" 146 | -D "TEST_ADD_LABELS=${_ADD_LABELS}" 147 | -D "TEST_PREFIX=${_TEST_PREFIX}" 148 | -D "TEST_SUFFIX=${_TEST_SUFFIX}" 149 | -D "TEST_LIST=${_TEST_LIST}" 150 | -D "TEST_JUNIT_OUTPUT_DIR=${_JUNIT_OUTPUT_DIR}" 151 | -D "CTEST_FILE=${ctest_tests_file}" 152 | -P "${_DOCTEST_DISCOVER_TESTS_SCRIPT}" 153 | VERBATIM 154 | ) 155 | 156 | file(WRITE "${ctest_include_file}" 157 | "if(EXISTS \"${ctest_tests_file}\")\n" 158 | " include(\"${ctest_tests_file}\")\n" 159 | "else()\n" 160 | " add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n" 161 | "endif()\n" 162 | ) 163 | 164 | if(NOT CMAKE_VERSION VERSION_LESS 3.10) 165 | # Add discovered tests to directory TEST_INCLUDE_FILES 166 | set_property(DIRECTORY 167 | APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}" 168 | ) 169 | else() 170 | # Add discovered tests as directory TEST_INCLUDE_FILE if possible 171 | get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET) 172 | if(NOT ${test_include_file_set}) 173 | set_property(DIRECTORY 174 | PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}" 175 | ) 176 | else() 177 | message(FATAL_ERROR 178 | "Cannot set more than one TEST_INCLUDE_FILE" 179 | ) 180 | endif() 181 | endif() 182 | 183 | endfunction() 184 | 185 | ############################################################################### 186 | 187 | set(_DOCTEST_DISCOVER_TESTS_SCRIPT 188 | ${CMAKE_CURRENT_LIST_DIR}/doctestAddTests.cmake 189 | ) 190 | -------------------------------------------------------------------------------- /test/cmake/doctestAddTests.cmake: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved BSD 3-Clause License. See accompanying 2 | # file Copyright.txt or https://cmake.org/licensing for details. 3 | 4 | set(prefix "${TEST_PREFIX}") 5 | set(suffix "${TEST_SUFFIX}") 6 | set(spec ${TEST_SPEC}) 7 | set(extra_args ${TEST_EXTRA_ARGS}) 8 | set(properties ${TEST_PROPERTIES}) 9 | set(add_labels ${TEST_ADD_LABELS}) 10 | set(junit_output_dir "${TEST_JUNIT_OUTPUT_DIR}") 11 | set(script) 12 | set(suite) 13 | set(tests) 14 | 15 | function(add_command NAME) 16 | set(_args "") 17 | foreach(_arg ${ARGN}) 18 | if(_arg MATCHES "[^-./:a-zA-Z0-9_]") 19 | set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument 20 | else() 21 | set(_args "${_args} ${_arg}") 22 | endif() 23 | endforeach() 24 | set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE) 25 | endfunction() 26 | 27 | # Run test executable to get list of available tests 28 | if(NOT EXISTS "${TEST_EXECUTABLE}") 29 | message(FATAL_ERROR 30 | "Specified test executable '${TEST_EXECUTABLE}' does not exist" 31 | ) 32 | endif() 33 | 34 | if("${spec}" MATCHES .) 35 | set(spec "--test-case=${spec}") 36 | endif() 37 | 38 | execute_process( 39 | COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-test-cases 40 | OUTPUT_VARIABLE output 41 | RESULT_VARIABLE result 42 | WORKING_DIRECTORY "${TEST_WORKING_DIR}" 43 | ) 44 | if(NOT ${result} EQUAL 0) 45 | message(FATAL_ERROR 46 | "Error running test executable '${TEST_EXECUTABLE}':\n" 47 | " Result: ${result}\n" 48 | " Output: ${output}\n" 49 | ) 50 | endif() 51 | 52 | string(REPLACE "\n" ";" output "${output}") 53 | 54 | # Parse output 55 | foreach(line ${output}) 56 | if("${line}" STREQUAL "===============================================================================" OR "${line}" MATCHES [==[^\[doctest\] ]==]) 57 | continue() 58 | endif() 59 | set(test ${line}) 60 | set(labels "") 61 | if(${add_labels}) 62 | # get test suite that test belongs to 63 | execute_process( 64 | COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" --test-case=${test} --list-test-suites 65 | OUTPUT_VARIABLE labeloutput 66 | RESULT_VARIABLE labelresult 67 | WORKING_DIRECTORY "${TEST_WORKING_DIR}" 68 | ) 69 | if(NOT ${labelresult} EQUAL 0) 70 | message(FATAL_ERROR 71 | "Error running test executable '${TEST_EXECUTABLE}':\n" 72 | " Result: ${labelresult}\n" 73 | " Output: ${labeloutput}\n" 74 | ) 75 | endif() 76 | 77 | string(REPLACE "\n" ";" labeloutput "${labeloutput}") 78 | foreach(labelline ${labeloutput}) 79 | if("${labelline}" STREQUAL "===============================================================================" OR "${labelline}" MATCHES [==[^\[doctest\] ]==]) 80 | continue() 81 | endif() 82 | list(APPEND labels ${labelline}) 83 | endforeach() 84 | endif() 85 | 86 | if(NOT "${junit_output_dir}" STREQUAL "") 87 | # turn testname into a valid filename by replacing all special characters with "-" 88 | string(REGEX REPLACE "[/\\:\"|<>]" "-" test_filename "${test}") 89 | set(TEST_JUNIT_OUTPUT_PARAM "--reporters=junit" "--out=${junit_output_dir}/${prefix}${test_filename}${suffix}.xml") 90 | else() 91 | unset(TEST_JUNIT_OUTPUT_PARAM) 92 | endif() 93 | # use escape commas to handle properly test cases with commas inside the name 94 | string(REPLACE "," "\\," test_name ${test}) 95 | # ...and add to script 96 | add_command(add_test 97 | "${prefix}${test}${suffix}" 98 | ${TEST_EXECUTOR} 99 | "${TEST_EXECUTABLE}" 100 | "--test-case=${test_name}" 101 | "${TEST_JUNIT_OUTPUT_PARAM}" 102 | ${extra_args} 103 | ) 104 | add_command(set_tests_properties 105 | "${prefix}${test}${suffix}" 106 | PROPERTIES 107 | WORKING_DIRECTORY "${TEST_WORKING_DIR}" 108 | ${properties} 109 | LABELS ${labels} 110 | ) 111 | unset(labels) 112 | list(APPEND tests "${prefix}${test}${suffix}") 113 | endforeach() 114 | 115 | # Create a list of all discovered tests, which users may use to e.g. set 116 | # properties on the tests 117 | add_command(set ${TEST_LIST} ${tests}) 118 | 119 | # Write CTest script 120 | file(WRITE "${CTEST_FILE}" "${script}") 121 | -------------------------------------------------------------------------------- /test/source/stat_other.hpp: -------------------------------------------------------------------------------- 1 | #ifndef VSTAT_STAT_OTHER_HPP 2 | #define VSTAT_STAT_OTHER_HPP 3 | 4 | #include 5 | #include 6 | 7 | // boost.accumulator 8 | #include 9 | #include 10 | 11 | // boost.math toolkit 12 | #include 13 | #include 14 | 15 | // GNU GSL 16 | #include 17 | #include 18 | 19 | // Linasm 20 | #include 21 | 22 | namespace stat_other { 23 | template 24 | using vec = std::vector; 25 | using vec_d = vec; 26 | using vec_f = vec; 27 | 28 | namespace gsl { 29 | // double variants 30 | inline auto mean(vec_d const& x) { 31 | return gsl_stats_mean(x.data(), 1UL, x.size()); 32 | } 33 | 34 | inline auto mean(vec_d const& x, vec_d const& w) { 35 | return gsl_stats_wmean(w.data(), 1UL, x.data(), 1UL, x.size()); 36 | } 37 | 38 | inline auto sum(vec_d const& x) { 39 | return static_cast(x.size()) * mean(x); 40 | } 41 | 42 | inline auto sum(vec_d const& x, vec_d const& w) { 43 | return static_cast(w.size()) * mean(w) * mean(x, w); 44 | } 45 | 46 | inline auto variance(vec_d const& x) { 47 | return gsl_stats_variance(x.data(), 1UL, x.size()); 48 | } 49 | 50 | inline auto variance(vec_d const& x, vec_d const& w) { 51 | return gsl_stats_wvariance(w.data(), 1UL, x.data(), 1UL, x.size()); 52 | } 53 | 54 | inline auto covariance(vec_d const& x, vec_d const& y) { 55 | return gsl_stats_covariance(x.data(), 1UL, y.data(), 1UL, x.size()); 56 | } 57 | 58 | inline auto covariance(vec_d const& /*unused*/, vec_d const& /*unused*/, vec_d const& /*unused*/) { 59 | throw std::runtime_error("not supported"); 60 | } 61 | 62 | inline auto correlation(vec_d const& x, vec_d const& y) { 63 | return gsl_stats_correlation(x.data(), 1UL, y.data(), 1UL, x.size()); 64 | } 65 | 66 | inline auto correlation(vec_d const& /*unused*/, vec_d const& /*unused*/, vec_d const& /*unused*/) { 67 | throw std::runtime_error("not supported"); 68 | } 69 | 70 | // float variants 71 | inline auto mean(vec_f const& x) { 72 | return gsl_stats_float_mean(x.data(), 1UL, x.size()); 73 | } 74 | 75 | inline auto mean(vec_f const& x, vec_f const& w) { 76 | return gsl_stats_float_wmean(w.data(), 1UL, x.data(), 1UL, x.size()); 77 | } 78 | 79 | inline auto sum(vec_f const& x) { 80 | return static_cast(x.size()) * mean(x); 81 | } 82 | 83 | inline auto sum(vec_f const& x, vec_f const& w) { 84 | return static_cast(w.size()) * mean(w) * mean(x, w); 85 | } 86 | 87 | inline auto variance(vec_f const& x) { 88 | return gsl_stats_float_variance(x.data(), 1UL, x.size()); 89 | } 90 | 91 | inline auto variance(vec_f const& x, vec_f const& w) { 92 | return gsl_stats_float_wvariance(w.data(), 1UL, x.data(), 1UL, x.size()); 93 | } 94 | 95 | inline auto covariance(vec_f const& x, vec_f const& y) { 96 | return gsl_stats_float_covariance(x.data(), 1UL, y.data(), 1UL, x.size()); 97 | } 98 | 99 | inline auto covariance(vec_f const& /*unused*/, vec_f const& /*unused*/, vec_f const& /*unused*/) { 100 | throw std::runtime_error("not supported"); 101 | } 102 | 103 | inline auto correlation(vec_f const& x, vec_f const& y) { 104 | return gsl_stats_float_correlation(x.data(), 1UL, y.data(), 1UL, x.size()); 105 | } 106 | 107 | inline auto correlation(vec_f const& /*unused*/, vec_f const& /*unused*/, vec_f const& /*unused*/) { 108 | throw std::runtime_error("not supported"); 109 | } 110 | } // namespace gsl 111 | 112 | namespace boost { 113 | namespace ba = ::boost::accumulators; 114 | 115 | template 116 | inline auto mean(vec const& x) { 117 | ba::accumulator_set> acc; 118 | for (auto i = 0; i < std::ssize(x); ++i) { acc(x[i]); } 119 | return ba::mean(acc); 120 | } 121 | 122 | template 123 | inline auto mean(vec const& x, vec const& w) { 124 | ba::accumulator_set, T> acc; 125 | for (auto i = 0; i < std::ssize(x); ++i) { acc(x[i], ba::weight=w[i]); } 126 | return ba::weighted_mean(acc); 127 | } 128 | 129 | template 130 | inline auto variance(vec const& x) { 131 | ba::accumulator_set> acc; 132 | for (auto i = 0; i < std::ssize(x); ++i) { acc(x[i]); } 133 | return ba::variance(acc); 134 | } 135 | 136 | template 137 | inline auto variance(vec const& x, vec const& w) { 138 | ba::accumulator_set, T> acc; 139 | for (auto i = 0; i < std::ssize(x); ++i) { acc(x[i], ba::weight=w[i]); } 140 | return ba::weighted_variance(acc); 141 | } 142 | 143 | template 144 | inline auto covariance(vec const& x, vec const& y) { 145 | ba::accumulator_set>> acc; 146 | for (auto i = 0; i < std::ssize(x); ++i) { acc(x[i], ba::covariate1=y[i]); } 147 | return ba::covariance(acc); 148 | } 149 | 150 | template 151 | inline auto covariance(vec const& x, vec const& y, vec const& w) { 152 | ba::accumulator_set>, T> acc; 153 | for (auto i = 0; i < std::ssize(x); ++i) { acc(x[i], ba::weight=w[i], ba::covariate1=y[i]); } 154 | return ba::weighted_covariance(acc); 155 | } 156 | 157 | template 158 | inline auto r2_score(vec const& x, vec const& y) { 159 | auto m = mean(y); 160 | 161 | ba::accumulator_set> acc1; 162 | ba::accumulator_set> acc2; 163 | for (auto i = 0; i < x.size(); ++i) { 164 | auto e = x[i] - y[i]; 165 | auto t = y[i] - m; 166 | acc1(e*e); 167 | acc2(t*t); 168 | } 169 | auto const rss = ba::sum(acc1); 170 | auto const tss = ba::sum(acc2); 171 | 172 | return tss < std::numeric_limits::epsilon() 173 | ? std::numeric_limits::lowest() 174 | : 1.0 - rss / tss; 175 | } 176 | 177 | template 178 | inline auto r2_score(vec const& x, vec const& y, vec const z) { 179 | ba::accumulator_set, T> acc1; 180 | ba::accumulator_set, T> acc2; 181 | ba::accumulator_set> acc3; 182 | for (auto i = 0; i < x.size(); ++i) { 183 | auto e = x[i] - y[i]; 184 | acc1(e*e, ba::weight=z[i]); 185 | acc2(y[i], ba::weight=z[i]); 186 | acc3(z[i]); 187 | } 188 | auto const sum = ba::sum(acc3); 189 | auto const rss = ba::weighted_sum(acc1); 190 | auto const tss = ba::weighted_variance(acc2) * sum; 191 | 192 | return tss < std::numeric_limits::epsilon() 193 | ? std::numeric_limits::lowest() 194 | : 1.0 - rss / tss; 195 | } 196 | } // namespace boost 197 | 198 | namespace linasm { 199 | template 200 | inline auto mean(vec const& x) { 201 | return Statistics::Mean(x.data(), x.size()); 202 | } 203 | 204 | template 205 | inline auto mean(vec const& x, vec const& w) { 206 | throw std::runtime_error("not supported"); 207 | } 208 | 209 | template 210 | inline auto sum(vec const& x) { 211 | return static_cast(x.size()) * mean(x); 212 | } 213 | 214 | template 215 | inline auto sum(vec const& x, vec const& w) { 216 | return static_cast(w.size()) * mean(w) * mean(x, w); 217 | } 218 | 219 | template 220 | inline auto variance(vec const& x) { 221 | return Statistics::Variance(x.data(), x.size(), mean(x)); 222 | } 223 | 224 | template 225 | inline auto variance(vec const& x, vec const& w) { 226 | throw std::runtime_error("not supported"); 227 | } 228 | 229 | template 230 | inline auto covariance(vec const& x, vec const& y) { 231 | return Statistics::Covariance(x.data(), y.data(), x.size(), mean(x), mean(y)); 232 | } 233 | 234 | template 235 | inline auto covariance(vec const& /*unused*/, vec const& /*unused*/, vec const& /*unused*/) { 236 | throw std::runtime_error("not supported"); 237 | } 238 | 239 | template 240 | inline auto correlation(vec const& x, vec const& y) { 241 | return Statistics::PearsonCorrelation(x.data(), y.data(), x.size(), mean(x), mean(y)); 242 | } 243 | 244 | template 245 | inline auto correlation(vec const& /*unused*/, vec const& /*unused*/, vec const& /*unused*/) { 246 | throw std::runtime_error("not supported"); 247 | } 248 | } // namespace linasm 249 | } // namespace stat_other 250 | 251 | #endif 252 | -------------------------------------------------------------------------------- /test/source/vstat_test.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // SPDX-FileCopyrightText: Copyright 2019-2024 Heal Research 3 | 4 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 5 | #include 6 | 7 | #define ANKERL_NANOBENCH_IMPLEMENT 8 | #include "nanobench.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | #include "vstat/vstat.hpp" 17 | #include "stat_other.hpp" 18 | 19 | namespace nb = ankerl::nanobench; 20 | namespace dt = doctest; 21 | 22 | namespace uv = vstat::univariate; 23 | namespace bv = vstat::bivariate; 24 | 25 | namespace VSTAT_NAMESPACE::test { 26 | namespace util { 27 | template 28 | auto generate(auto& rng, int count, T min = T{0}, T max = T{1}) { 29 | std::vector vec(count); 30 | std::generate(vec.begin(), vec.end(), [&]() { return std::uniform_real_distribution(min, max)(rng); }); 31 | return vec; 32 | } 33 | } // namespace util 34 | 35 | template 36 | auto equal(T a, T b, T eps = std::numeric_limits::epsilon()) { return std::abs(a-b) < eps; }; 37 | 38 | auto constexpr count_small{10}; // small 39 | auto constexpr count_medium{1'000}; // medium 40 | auto constexpr count_large{100'000}; // large 41 | 42 | 43 | 44 | TEST_CASE("gamma") { 45 | eve::wide x{10}; 46 | eve::wide g = eve::log_abs_gamma(x); 47 | std::cout << "x = " << x << "\n"; 48 | std::cout << "g = " << g << "\n"; 49 | } 50 | 51 | TEST_CASE("univariate" * dt::test_suite("[correctness]")) { 52 | float x[] = { 1.F, 1.F, 2.F, 6.F }; 53 | float y[] = { 2.F, 4.F, 3.F, 1.F }; 54 | size_t n = std::size(x); 55 | 56 | auto stats = vstat::bivariate::accumulate(x, x+n, std::begin(y)); 57 | std::cout << "stats:\n" << stats << "\n"; 58 | } 59 | 60 | TEST_CASE("r2" * dt::test_suite("[correctness]")) { 61 | std::default_random_engine rng{1234}; 62 | 63 | auto test_r2 = [&](int n, T eps) { 64 | auto x = util::generate(rng, n); 65 | auto y = util::generate(rng, n); 66 | 67 | auto m1 = stat_other::boost::r2_score(x, y); 68 | auto m2 = vstat::metrics::r2_score(x.begin(), x.end(), y.begin()); 69 | 70 | CAPTURE(n); 71 | CAPTURE(m1); 72 | CAPTURE(m2); 73 | REQUIRE(equal(m1, m2, eps)); 74 | }; 75 | 76 | SUBCASE("double") { 77 | double const eps{1e-6}; 78 | SUBCASE("small") { test_r2(count_small, eps); } // NOLINT 79 | SUBCASE("medium") { test_r2(count_medium, eps); } // NOLINT 80 | SUBCASE("large") { test_r2(count_large, eps); } // NOLINT 81 | } 82 | 83 | SUBCASE("float") { 84 | float const eps{1e-5}; 85 | SUBCASE("small") { test_r2.operator()(count_small, eps); } // NOLINT 86 | SUBCASE("medium") { test_r2.operator()(count_medium, eps); } // NOLINT 87 | SUBCASE("large") { test_r2.operator()(count_large, eps); } // NOLINT 88 | } 89 | } 90 | 91 | TEST_CASE("weighted r2" * dt::test_suite("[correctness]")) { 92 | std::default_random_engine rng{1234}; 93 | 94 | auto test_r2_weighted = [&](int n, T eps) { 95 | auto x = util::generate(rng, n); 96 | auto y = util::generate(rng, n); 97 | auto z = util::generate(rng, n); 98 | 99 | auto m1 = stat_other::boost::r2_score(x, y, z); 100 | auto m2 = vstat::metrics::r2_score(x.begin(), x.end(), y.begin(), z.begin()); 101 | 102 | CAPTURE(n); 103 | CAPTURE(m1); 104 | CAPTURE(m2); 105 | REQUIRE(equal(m1, m2, eps)); 106 | }; 107 | 108 | SUBCASE("double") { 109 | double const eps{1e-1}; 110 | SUBCASE("small") { test_r2_weighted(count_small, eps); } // NOLINT 111 | SUBCASE("medium") { test_r2_weighted(count_medium, eps); } // NOLINT 112 | SUBCASE("large") { test_r2_weighted(count_large, eps); } // NOLINT 113 | } 114 | 115 | SUBCASE("float") { 116 | float const eps{1e-1}; 117 | SUBCASE("small") { test_r2_weighted.operator()(count_small, eps); } // NOLINT 118 | SUBCASE("medium") { test_r2_weighted.operator()(count_medium, eps); } // NOLINT 119 | SUBCASE("large") { test_r2_weighted.operator()(count_large, eps); } // NOLINT 120 | } 121 | } 122 | 123 | TEST_CASE("mean" * dt::test_suite("[correctness]")) { 124 | std::random_device rng{}; 125 | 126 | auto test_mean = [&](int n, T eps) { 127 | auto x = util::generate(rng, n); 128 | 129 | auto m1 = stat_other::boost::mean(x); 130 | auto m2 = uv::accumulate(x.begin(), x.end()).mean; 131 | 132 | CAPTURE(m1); 133 | CAPTURE(m2); 134 | REQUIRE(equal(m1, m2, eps)); 135 | }; 136 | 137 | SUBCASE("double") { 138 | double const eps{1e-6}; 139 | SUBCASE("small") { test_mean(count_small, eps); } // NOLINT 140 | SUBCASE("medium") { test_mean(count_medium, eps); } // NOLINT 141 | SUBCASE("large") { test_mean(count_large, eps); } // NOLINT 142 | } 143 | 144 | SUBCASE("float") { 145 | float const eps{1e-5}; 146 | SUBCASE("small") { test_mean.operator()(count_small, eps); } // NOLINT 147 | SUBCASE("medium") { test_mean.operator()(count_medium, eps); } // NOLINT 148 | SUBCASE("large") { test_mean.operator()(count_large, eps); } // NOLINT 149 | } 150 | } 151 | 152 | TEST_CASE("weighted mean" * dt::test_suite("[correctness]")) { 153 | std::random_device rng{}; 154 | 155 | auto test_mean = [&](int n, T eps) { 156 | auto x = util::generate(rng, n); 157 | auto w = util::generate(rng, n); 158 | 159 | auto m1 = stat_other::boost::mean(x, w); 160 | auto m2 = uv::accumulate(x.begin(), x.end(), w.begin()).mean; 161 | 162 | CAPTURE(m1); 163 | CAPTURE(m2); 164 | REQUIRE(equal(m1, m2, eps)); 165 | }; 166 | 167 | SUBCASE("double") { 168 | double const eps{1e-6}; 169 | SUBCASE("small") { test_mean(count_small, eps); } // NOLINT 170 | SUBCASE("medium") { test_mean(count_medium, eps); } // NOLINT 171 | SUBCASE("large") { test_mean(count_large, eps); } // NOLINT 172 | } 173 | 174 | SUBCASE("float") { 175 | float const eps{1e-5}; 176 | SUBCASE("small") { test_mean.operator()(count_small, eps); } // NOLINT 177 | SUBCASE("medium") { test_mean.operator()(count_medium, eps); } // NOLINT 178 | SUBCASE("large") { test_mean.operator()(count_large, eps); } // NOLINT 179 | } 180 | } 181 | 182 | TEST_CASE("variance" * dt::test_suite("[correctness]")) { 183 | std::random_device rng{}; 184 | 185 | auto test_variance = [&](int n, T eps) { 186 | auto x = util::generate(rng, n); 187 | auto y = util::generate(rng, n); 188 | 189 | auto m1 = stat_other::boost::variance(x, y); 190 | auto m2 = uv::accumulate(x.begin(), x.end(), y.begin()).variance; 191 | 192 | CAPTURE(m1); 193 | CAPTURE(m2); 194 | REQUIRE(equal(m1, m2, eps)); 195 | }; 196 | 197 | SUBCASE("double") { 198 | double const eps{1e-6}; 199 | SUBCASE("small") { test_variance(count_small, eps); } // NOLINT 200 | SUBCASE("medium") { test_variance(count_medium, eps); } // NOLINT 201 | SUBCASE("large") { test_variance(count_large, eps); } // NOLINT 202 | } 203 | 204 | SUBCASE("float") { 205 | float const eps{1e-5}; 206 | SUBCASE("small") { test_variance.operator()(count_small, eps); } // NOLINT 207 | SUBCASE("medium") { test_variance.operator()(count_medium, eps); } // NOLINT 208 | SUBCASE("large") { test_variance.operator()(count_large, eps); } // NOLINT 209 | } 210 | } 211 | 212 | TEST_CASE("weighted variance" * dt::test_suite("[correctness]")) { 213 | std::random_device rng{}; 214 | 215 | auto test_variance = [&](int n, T eps) { 216 | auto x = util::generate(rng, n); 217 | auto w = util::generate(rng, n); 218 | 219 | auto m1 = stat_other::boost::variance(x, w); 220 | auto m2 = uv::accumulate(x.begin(), x.end(), w.begin()).variance; 221 | 222 | CAPTURE(m1); 223 | CAPTURE(m2); 224 | REQUIRE(equal(m1, m2, eps)); 225 | }; 226 | 227 | SUBCASE("double") { 228 | double const eps{1e-6}; 229 | SUBCASE("small") { test_variance(count_small, eps); } // NOLINT 230 | SUBCASE("medium") { test_variance(count_medium, eps); } // NOLINT 231 | SUBCASE("large") { test_variance(count_large, eps); } // NOLINT 232 | } 233 | 234 | SUBCASE("float") { 235 | float const eps{1e-5}; 236 | SUBCASE("small") { test_variance.operator()(count_small, eps); } // NOLINT 237 | SUBCASE("medium") { test_variance.operator()(count_medium, eps); } // NOLINT 238 | SUBCASE("large") { test_variance.operator()(count_large, eps); } // NOLINT 239 | } 240 | } 241 | 242 | TEST_CASE("covariance" * dt::test_suite("[correctness]")) { 243 | std::random_device rng{}; 244 | 245 | auto test_covariance = [&](int n, T eps) { 246 | auto x = util::generate(rng, n); 247 | auto y = util::generate(rng, n); 248 | 249 | auto m1 = stat_other::boost::covariance(x, y); 250 | auto m2 = bv::accumulate(x.begin(), x.end(), y.begin()).covariance; 251 | 252 | CAPTURE(m1); 253 | CAPTURE(m2); 254 | REQUIRE(equal(m1, m2, eps)); 255 | }; 256 | 257 | SUBCASE("double") { 258 | double const eps{1e-6}; 259 | SUBCASE("small") { test_covariance(count_small, eps); } // NOLINT 260 | SUBCASE("medium") { test_covariance(count_medium, eps); } // NOLINT 261 | SUBCASE("large") { test_covariance(count_large, eps); } // NOLINT 262 | } 263 | 264 | SUBCASE("float") { 265 | float const eps{1e-5}; 266 | SUBCASE("small") { test_covariance.operator()(count_small, eps); } // NOLINT 267 | SUBCASE("medium") { test_covariance.operator()(count_medium, eps); } // NOLINT 268 | SUBCASE("large") { test_covariance.operator()(count_large, eps); } // NOLINT 269 | } 270 | } 271 | 272 | TEST_CASE("weighted covariance" * dt::test_suite("[correctness]")) { 273 | std::random_device rng{}; 274 | 275 | auto test_covariance = [&](int n, T eps) { 276 | auto x = util::generate(rng, n); 277 | auto y = util::generate(rng, n); 278 | auto w = util::generate(rng, n); 279 | 280 | auto m1 = stat_other::boost::covariance(x, y, w); 281 | auto m2 = bv::accumulate(x.begin(), x.end(), y.begin(), w.begin()).covariance; 282 | 283 | CAPTURE(m1); 284 | CAPTURE(m2); 285 | REQUIRE(equal(m1, m2, eps)); 286 | }; 287 | 288 | SUBCASE("double") { 289 | double const eps{1e-6}; 290 | SUBCASE("small") { test_covariance(count_small, eps); } // NOLINT 291 | SUBCASE("medium") { test_covariance(count_medium, eps); } // NOLINT 292 | SUBCASE("large") { test_covariance(count_large, eps); } // NOLINT 293 | } 294 | 295 | SUBCASE("float") { 296 | float const eps{1e-5}; 297 | SUBCASE("small") { test_covariance.operator()(count_small, eps); } // NOLINT 298 | SUBCASE("medium") { test_covariance.operator()(count_medium, eps); } // NOLINT 299 | SUBCASE("large") { test_covariance.operator()(count_large, eps); } // NOLINT 300 | } 301 | } 302 | 303 | TEST_CASE("benchmarks" * dt::test_suite("[performance]")) { 304 | std::random_device rng{}; 305 | 306 | nb::Bench bench; 307 | auto const n{1000}; 308 | for (auto s = n; s <= 1'024'000; s *= 2) { 309 | // mean & weighted mean 310 | auto xd = util::generate(rng, n); 311 | auto yd = util::generate(rng, n); 312 | auto wd = util::generate(rng, n); 313 | 314 | auto xf = util::generate(rng, n); 315 | auto yf = util::generate(rng, n); 316 | auto wf = util::generate(rng, n); 317 | 318 | double m{0.0}; 319 | 320 | bench.batch(s).run("vstat;mean;double", [&]() { 321 | m += uv::accumulate(xd.begin(), xd.end()).mean; 322 | }); 323 | 324 | bench.batch(s).run("vstat;weighted mean;double", [&]() { 325 | m += uv::accumulate(xd.begin(), xd.end(), wd.begin()).mean; 326 | }); 327 | 328 | bench.batch(s).run("vstat;variance;double", [&]() { 329 | m += uv::accumulate(xd.begin(), xd.end()).variance; 330 | }); 331 | 332 | bench.batch(s).run("vstat;weighted variance;double", [&]() { 333 | m += uv::accumulate(xd.begin(), xd.end(), wd.begin()).variance; 334 | }); 335 | 336 | bench.batch(s).run("vstat;covariance;double", [&]() { 337 | m += bv::accumulate(xd.begin(), xd.end(), yd.begin()).covariance; 338 | }); 339 | 340 | bench.batch(s).run("vstat;weighted covariance;double", [&]() { 341 | m += bv::accumulate(xd.begin(), xd.end(), yd.begin(), wd.begin()).covariance; 342 | }); 343 | 344 | bench.batch(s).run("boost.accu;mean;double", [&]() { 345 | m += stat_other::boost::mean(xd); 346 | }); 347 | 348 | bench.batch(s).run("boost.accu;weighted mean;double", [&]() { 349 | m += stat_other::boost::mean(xd, wd); 350 | }); 351 | 352 | bench.batch(s).run("boost.accu;variance;double", [&]() { 353 | m += stat_other::boost::variance(xd); 354 | }); 355 | 356 | bench.batch(s).run("boost.accu;weighted variance;double", [&]() { 357 | m += stat_other::boost::variance(xd, wd); 358 | }); 359 | 360 | bench.batch(s).run("boost.accu;covariance;double", [&]() { 361 | m += stat_other::boost::covariance(xd, yd); 362 | }); 363 | 364 | bench.batch(s).run("boost.accu;weighted covariance;double", [&]() { 365 | m += stat_other::boost::covariance(xd, yd, wd); 366 | }); 367 | 368 | bench.batch(s).run("boost.math;mean;double", [&]() { 369 | m += boost::math::statistics::mean(xd); 370 | }); 371 | 372 | bench.batch(s).run("boost.math;variance;double", [&]() { 373 | m += boost::math::statistics::variance(xd); 374 | }); 375 | 376 | bench.batch(s).run("boost.math;covariance;double", [&]() { 377 | m += boost::math::statistics::covariance(xd, yd); 378 | }); 379 | 380 | bench.batch(s).run("gsl;mean;double", [&]() { 381 | m += stat_other::gsl::mean(xd); 382 | }); 383 | 384 | bench.batch(s).run("gsl;variance;double", [&]() { 385 | m += stat_other::gsl::variance(xd); 386 | }); 387 | 388 | bench.batch(s).run("gsl;covariance;double", [&]() { 389 | m += stat_other::gsl::covariance(xd, yd); 390 | }); 391 | 392 | bench.batch(s).run("linasm;mean;double", [&]() { 393 | m += stat_other::linasm::variance(xd); 394 | }); 395 | 396 | bench.batch(s).run("linasm;variance;double", [&]() { 397 | m += stat_other::linasm::variance(xd); 398 | }); 399 | 400 | bench.batch(s).run("linasm;covariance;double", [&]() { 401 | m += stat_other::linasm::covariance(xd, yd); 402 | }); 403 | 404 | bench.batch(s).run("vstat;mean;float", [&]() { 405 | m += uv::accumulate(xf.begin(), xf.end()).mean; 406 | }); 407 | 408 | bench.batch(s).run("vstat;weighted mean;float", [&]() { 409 | m += uv::accumulate(xf.begin(), xf.end(), wf.begin()).mean; 410 | }); 411 | 412 | bench.batch(s).run("vstat;variance;float", [&]() { 413 | m += uv::accumulate(xf.begin(), xf.end()).variance; 414 | }); 415 | 416 | bench.batch(s).run("vstat;weighted variance;float", [&]() { 417 | m += uv::accumulate(xf.begin(), xf.end(), wf.begin()).variance; 418 | }); 419 | 420 | bench.batch(s).run("vstat;covariance;float", [&]() { 421 | m += bv::accumulate(xf.begin(), xf.end(), yf.begin()).covariance; 422 | }); 423 | 424 | bench.batch(s).run("vstat;weighted covariance;float", [&]() { 425 | m += bv::accumulate(xf.begin(), xf.end(), yf.begin(), wf.begin()).covariance; 426 | }); 427 | 428 | bench.batch(s).run("boost.accu;mean;float", [&]() { 429 | m += stat_other::boost::mean(xf); 430 | }); 431 | 432 | bench.batch(s).run("boost.accu;weighted mean;float", [&]() { 433 | m += stat_other::boost::mean(xf, wf); 434 | }); 435 | 436 | bench.batch(s).run("boost.accu;variance;float", [&]() { 437 | m += stat_other::boost::variance(xf); 438 | }); 439 | 440 | bench.batch(s).run("boost.accu;weighted variance;float", [&]() { 441 | m += stat_other::boost::variance(xf, wf); 442 | }); 443 | 444 | bench.batch(s).run("boost.accu;covariance;float", [&]() { 445 | m += stat_other::boost::covariance(xf, yf); 446 | }); 447 | 448 | bench.batch(s).run("boost.accu;weighted covariance;float", [&]() { 449 | m += stat_other::boost::covariance(xf, yf, wf); 450 | }); 451 | 452 | bench.batch(s).run("boost.math;mean;float", [&]() { 453 | m += boost::math::statistics::mean(xf); 454 | }); 455 | 456 | bench.batch(s).run("boost.math;variance;float", [&]() { 457 | m += boost::math::statistics::variance(xf); 458 | }); 459 | 460 | bench.batch(s).run("boost.math;covariance;float", [&]() { 461 | m += boost::math::statistics::covariance(xf, yf); 462 | }); 463 | 464 | bench.batch(s).run("gsl;mean;float", [&]() { 465 | m += stat_other::gsl::mean(xf); 466 | }); 467 | 468 | bench.batch(s).run("gsl;variance;float", [&]() { 469 | m += stat_other::gsl::variance(xf); 470 | }); 471 | 472 | bench.batch(s).run("gsl;covariance;float", [&]() { 473 | m += stat_other::gsl::covariance(xf, yf); 474 | }); 475 | 476 | bench.batch(s).run("linasm;mean;float", [&]() { 477 | m += stat_other::linasm::variance(xf); 478 | }); 479 | 480 | bench.batch(s).run("linasm;variance;float", [&]() { 481 | m += stat_other::linasm::variance(xf); 482 | }); 483 | 484 | bench.batch(s).run("linasm;covariance;float", [&]() { 485 | m += stat_other::linasm::covariance(xf, yf); 486 | }); 487 | } 488 | 489 | bench.render(nb::templates::csv(), std::cout); 490 | // format this result like this: 491 | // ./build/test/vstat_test | grep 'benchmark";' | tr -d '"' | column -t -s';' -o',' 492 | } 493 | } // namespace VSTAT_NAMESPACE::test 494 | -------------------------------------------------------------------------------- /vstat/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # SPDX-FileCopyrightText: Copyright 2019-2024 Heal Research 3 | 4 | from .vstat import * 5 | --------------------------------------------------------------------------------