├── .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 | [](https://github.com/heal-research/vstat/actions/workflows/build-linux.yml)
9 | [](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 | 
216 | 
217 | 
218 | 
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 |
59 | $projectname $projectnumber
60 |
61 | $projectbrief
62 | |
63 |
64 |
65 |
66 |
67 | $projectbrief
68 | |
69 |
70 |
71 |
72 |
73 |
74 | $searchbox |
75 |
76 |
77 |
78 |
79 |
80 |
81 | $searchbox |
82 |
83 |
84 |
85 |
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 |
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